Merge GE-Proton11-9 01bfbf67 (ge-master) into main

# Conflicts:
#	README.md
This commit is contained in:
The412Banner committed 2026-10-14 14:42:59 -04:00
commit de0c57f4a5
18 files changed
+1360 -13

No files matched your search

+1 -1
View File
@@ -1 +1 @@
GE-Proton11-7
GE-Proton11-9
+34 -11
View File
@@ -473,24 +473,45 @@ The build will be placed within the build directory as SOME-BUILD-NAME-HERE.tar.
## Options
###### Enable HDR
<a id="enable-hdr"></a>
It should work if you have:
###### HDR
- A compositor with HDR support
- A game with HDR support.
- A monitor with HDR support.
HDR is detected automatically; `PROTON_ENABLE_HDR=1` and `PROTON_USE_HDR=1`
are no longer required. The game, monitor, compositor, and Vulkan presentation
path must support HDR, and HDR must be enabled in the desktop or Gamescope.
This covers DX11 through DXVK and DX12 through VKD3D-Proton: both use the
DXVK-provided DXGI for output queries, with their respective Vulkan presenters
checking HDR surface support.
- Native Wine Wayland uses the compositor's per-output color-management state.
Select that driver with `PROTON_ENABLE_WAYLAND=1` when using a native Wayland
desktop outside Gamescope.
- Gamescope's XWayland path uses its HDR output feedback. Ordinary X11/XWayland
paths without HDR feedback default to SDR; this does not add HDR support to them.
- SDR outputs remain SDR, including when other connected monitors use HDR.
For native Wine Wayland without Gamescope, use:
```bash
PROTON_ENABLE_HDR=1 %command%
PROTON_ENABLE_WAYLAND=1 %command%
```
Remove `PROTON_ENABLE_HDR=1`, `PROTON_USE_HDR=1`, and `DXVK_HDR=1` from the
game's launch options to use automatic detection. This applies to both DX11
and DX12. When using Gamescope, keep its HDR configuration and use Wine's
default XWayland driver instead of adding the Wayland option above.
Use `DXVK_NO_HDR=1 %command%` to disable HDR reporting. The old Proton HDR
options are still accepted for compatibility; setting either to `0` disables
HDR (if both are set, `PROTON_USE_HDR` takes precedence). Explicit HDR opt-in
is retained for older X11 HDR WSI implementations without output feedback.
> [!NOTE]
> Setting PROTON_ENABLE_HDR=1 does NOT enable the Wine Wayland driver. Gamescope
> can use PROTON_ENABLE_HDR, and automatically enabling native Wine Wayland
> here would break that path. For HDR outside of gamescope, enable PROTON_ENABLE_HDR=1
> AND PROTON_ENABLE_WAYLAND=1 together only when using a native wayland desktop and
> not using gamescope.
> Automatic detection does not change your desktop's HDR setting, select Wine
> Wayland automatically, or force the game's own HDR setting on. Gamescope's
> HDR mode and WSI support still need to be configured normally.
##### Enabling NTSync
For NTSync to work, your kernel must be version 6.14 or newer and built with `CONFIG_NTSYNC=y` or `CONFIG_NTSYNC=m`. On non-systemd systems, you must also have a `ulimit -Hn` of 524288 or higher.
@@ -585,6 +606,8 @@ Without HIDRAW access, Wine retains its normal SDL fallback, which does not prov
| | `PROTON_STEAMINPUT_LAYOUT_DS4` | Force the fallback controller to use the DualShock 4 profile instead of automatic layout detection. |
| | `PROTON_STEAMINPUT_LAYOUT_DS5` | Force the fallback controller to use the DualSense profile instead of automatic layout detection. |
| `wayland` | `PROTON_USE_WAYLAND` or `PROTON_ENABLE_WAYLAND` | Enables the Wayland driver. |
| `hdr` | `PROTON_ENABLE_HDR` or `PROTON_USE_HDR` | Legacy HDR overrides; no longer required for automatic detection in DX11 or DX12. Set to `0` to disable HDR reporting. If both are set, `PROTON_USE_HDR` takes precedence. Does not select the Wayland driver. See [HDR](#hdr). |
| | `DXVK_NO_HDR` | Set to `1` to disable HDR reporting on both Wine display drivers, including for DX12 through the shared DXGI implementation. Takes precedence over HDR opt-in flags. |
| | `PROTON_USE_X11_EXCLUSIVE` | Under Wine-Wayland, use `winex11.drv` only for the executable specified by this value. Accepts an exact executable basename such as `Launcher.exe` or a case-insensitive Windows path fragment such as `Vendor\\Launcher.exe`; other processes remain on Wine-Wayland. |
| `winebth` | `PROTON_ENABLE_WINEBTH` | Enables `winebth.sys`, Wine's Bluetooth driver, which is disabled by default. Needed for Bluetooth LE devices such as smart trainers and controllers in Zwift or Rouvy. Requires BlueZ. |
| `wow64` | `PROTON_USE_WOW64` | Enables wow64. |
+61
View File
@@ -0,0 +1,61 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] dxgi: Detect output HDR state by default on Win32.
The active-output checks in dxgi-respect-output-hdr-state.patch already
prevent HDR10 from being reported for SDR or unqueryable outputs. Default
the Win32 HDR policy to enabled so these checks can detect HDR without
requiring PROTON_ENABLE_HDR, PROTON_USE_HDR, or DXVK_HDR.
Keep explicit DXVK_HDR and dxgi.enableHDR settings, and let DXVK_NO_HDR=1
disable HDR even when a configuration file enables it. Preserve the UE4
D3D11 compatibility exclusions, the Vulkan surface-format requirement,
and the existing opt-in policy for non-Win32 WSI backends.
Apply after dxgi-respect-output-hdr-state.patch. Wine Wayland already
reports compositor color-management state; the matching Wine fixup adds
Gamescope XWayland feedback and consistent disable handling.
Manual validation after rebuilding:
- Native Wayland HDR output, no HDR flags: game HDR becomes available.
- SDR output and mixed SDR/HDR monitors: only the HDR output reports HDR.
- Gamescope HDR on/off and ordinary XWayland SDR.
- DXVK_HDR=0, DXVK_NO_HDR=1, and dxgi.enableHDR=False.
- DX11, DX12 (which shares DXGI), and native Vulkan games using NVAPI.
---
diff --git a/src/dxgi/dxgi_options.cpp b/src/dxgi/dxgi_options.cpp
--- a/src/dxgi/dxgi_options.cpp
+++ b/src/dxgi/dxgi_options.cpp
@@ -199,7 +199,14 @@
this->hideIntelGpu = true;
}
- this->enableHDR = config.getOption<bool>("dxgi.enableHDR", env::getEnvVar("DXVK_HDR") == "1");
+ const std::string hdr = env::getEnvVar("DXVK_HDR");
+ bool enableHdrDefault = hdr == "1";
+#ifdef _WIN32
+ // Wine reports the active output state; HDR10 also needs surface support.
+ enableHdrDefault |= hdr.empty();
+#endif
+ this->enableHDR = config.getOption<bool>("dxgi.enableHDR", enableHdrDefault)
+ && env::getEnvVar("DXVK_NO_HDR") != "1";
bool enableUe4Workarounds = config.getOption<bool>("dxgi.enableUe4Workarounds", false);
diff --git a/dxvk.conf b/dxvk.conf
--- a/dxvk.conf
+++ b/dxvk.conf
@@ -11,7 +11,10 @@
# Many (broken) games will need this to be set to consider exposing HDR output
# as determine it based on the DXGIOutput's current ColorSpace instead of
# using CheckColorSpaceSupport.
-# This defaults to the value of the DXVK_HDR environment variable.
+# On Win32 this defaults to automatic detection of the active output's HDR state.
+# HDR10 presentation also requires support from the Vulkan surface.
+# DXVK_HDR=0 or dxgi.enableHDR=False disables it; DXVK_NO_HDR=1 takes precedence.
+# Other WSI backends retain the DXVK_HDR=1 opt-in.
#
# Supported values: True, False
@@ -0,0 +1,31 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] d3d9: Disable constant buffer streaming for StarCraft II.
StarCraft II intermittently hangs the GPU on RADV with an RX 5700 XT
using GE-Proton11-7. Replacing only DXVK with the GE-Proton10-34 version
avoids the failure. Disabling descriptor heaps and descriptor buffers was
not sufficient, but the reporter now reports success with only
d3d9.deviceLocalConstantBuffers = False on the newer build.
Disable constant buffer streaming in the application profile for SC2.exe
and SC2_x64.exe. Leave descriptor selection, Battle.net, other games and
the global default unchanged. User configuration can still override this
application default. This is a targeted compatibility workaround; the
underlying DXVK/driver failure has not been bisected to a specific commit.
Bug: https://github.com/GloriousEggroll/proton-ge-custom/issues/790
---
diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp
--- a/src/util/config/config.cpp
+++ b/src/util/config/config.cpp
@@ -534,6 +534,10 @@
}} },
+ /* StarCraft II: avoid GPU hangs with constant buffer streaming. */
+ { R"(\\SC2(_x64)?\.exe$)", {{
+ { "d3d9.deviceLocalConstantBuffers", "False" },
+ }} },
/* A Hat in Time */
{ R"(\\HatinTimeGame\.exe$)", {{
{ "d3d9.lenientClear", "True" },
}} },
@@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Alexandre Derumier <[email protected]>
Date: Sun, 11 Oct 2026 18:40:00 +0200
Subject: [PATCH] winedmo: Flush downstream before locking the pins when
seeking.
dmo_seeking_SetPositions() took each output pin's flushing_cs before
sending BeginFlush downstream. The stream thread holds that lock while it
delivers a sample (IMemInputPin_Receive), and a renderer may block in
Receive until it is flushed: the seek then waits for the stream thread,
which waits for the renderer, which waits for the flush. SNK Heroines Tag
Team Frenzy (its own renderer, looping movies) hung right after START.
Send BeginFlush first, as the Windows parsers and Wine's other parsers
do: the renderer returns from Receive, the stream thread releases the
lock, and the seek goes on.
---
dlls/winedmo/winedmo_quartz_parser.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/dlls/winedmo/winedmo_quartz_parser.c b/dlls/winedmo/winedmo_quartz_parser.c
--- a/dlls/winedmo/winedmo_quartz_parser.c
+++ b/dlls/winedmo/winedmo_quartz_parser.c
@@ -636,6 +636,11 @@
for (i = 0; i < filter->source_count; ++i)
WakeConditionVariable(&filter->sources[i]->queue_cv);
+ /* Flush downstream first: a stream thread holds its pin's flushing_cs during Receive, and
+ * a renderer may block in Receive until it is flushed (Windows parsers do the same). */
+ if (!(current_flags & AM_SEEKING_NoFlush))
+ dmo_parser_begin_downstream_flush(filter);
+
/* Acquire per-pin flushing_cs to ensure no Receive is in progress. */
for (i = 0; i < filter->source_count; ++i)
{
@@ -643,9 +648,6 @@
EnterCriticalSection(&filter->sources[i]->flushing_cs);
}
- if (!(current_flags & AM_SEEKING_NoFlush))
- dmo_parser_begin_downstream_flush(filter);
-
SourceSeekingImpl_SetPositions(iface, current, current_flags, stop, stop_flags);
/* Perform the actual seek. */
--
2.51.0
@@ -0,0 +1,139 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] mfmediaengine: Defer loop restarts until the session has ended.
Crimson Desert can stall at the title menu after titleview_03.mp4 reaches
EOF. Its media engine has looping enabled when MEEndOfPresentation arrives.
The handler immediately seeks back to zero, which resets the video sink's
sample queue before submitting the replay to the media session.
Patch 0059 correctly serializes replay against the old presentation's
draining. Resetting the sink before that drain completes can discard the
samples needed to request the remaining decoder output and end marker,
leaving both the drain and the queued replay waiting indefinitely.
In the failing trace, the replay is submitted at 156.587 but never reaches
session_commands_callback_Invoke. The game disables looping at 156.826,
then polls OnVideoStreamTick without receiving another frame for the rest
of the capture.
Do not restart loops on the source-level MEEndOfPresentation event. Keep
draining and let MESessionEnded perform the loop seek instead. If the
application disables looping meanwhile, that handler reports normal
completion. Preserve the early ended notification for non-looping sources,
which existing media-engine clients rely on, and keep session serialization.
Add coverage for two video-loop restarts followed by disabling looping and
waiting for normal completion.
References:
https://learn.microsoft.com/en-us/windows/win32/medfound/meendofpresentation
https://learn.microsoft.com/en-us/windows/win32/medfound/mesessionended
---
diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c
--- a/dlls/mfmediaengine/main.c
+++ b/dlls/mfmediaengine/main.c
@@ -1170,7 +1170,10 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result)
/* Media Engine users may wait on ended state before replacing the
* source. Network streams can deliver source end-of-presentation
* before every renderer has drained enough to produce MESessionEnded. */
- ended_event = media_engine_handle_ended(engine);
+ /* Loop seeks reset the sink's sample queue. Wait for MESessionEnded
+ * before doing that, otherwise the queued replay can wait forever
+ * for the samples and end-of-segment marker we still need to drain. */
+ ended_event = !(engine->flags & FLAGS_ENGINE_LOOP) && media_engine_handle_ended(engine);
LeaveCriticalSection(&engine->cs);
if (ended_event)
IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_ENDED, 0, 0);
diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c
index 0b815c7b8ed..ad32cfdf534 100644
--- a/dlls/mfmediaengine/tests/mfmediaengine.c
+++ b/dlls/mfmediaengine/tests/mfmediaengine.c
@@ -2979,6 +2979,78 @@ done:
IMFByteStream_Release(stream);
}
+static void test_looped_playback(void)
+{
+ struct test_seek_notify *notify = create_seek_notify();
+ IMFMediaEngineEx *media_engine = NULL;
+ IMFByteStream *stream = NULL;
+ LONGLONG pts, last_pts = -1;
+ unsigned int loops = 0;
+ DWORD start, res;
+ HRESULT hr;
+ BSTR url = NULL;
+
+ hr = create_media_engine(&notify->IMFMediaEngineNotify_iface, NULL, FALSE, DXGI_FORMAT_B8G8R8X8_UNORM,
+ &IID_IMFMediaEngineEx, (void **)&media_engine);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ if (FAILED(hr)) goto done;
+
+ stream = load_resource(L"i420-64x64.avi", L"video/avi");
+ url = SysAllocString(L"i420-64x64.avi");
+ hr = IMFMediaEngineEx_SetLoop(media_engine, TRUE);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, stream, url);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ if (FAILED(hr)) goto done;
+
+ hr = IMFMediaEngineEx_Play(media_engine);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ res = WaitForSingleObject(notify->playing_event, 5000);
+ ok(res == WAIT_OBJECT_0, "Waiting for playback returned %#lx.\n", res);
+ if (res != WAIT_OBJECT_0 || FAILED(notify->error)) goto done;
+
+ /* Consuming the final samples must allow replay to start after the sink drains. */
+ start = GetTickCount();
+ while (loops < 2 && GetTickCount() - start < 5000)
+ {
+ hr = IMFMediaEngineEx_OnVideoStreamTick(media_engine, &pts);
+ ok(hr == S_OK || hr == S_FALSE, "Unexpected tick hr %#lx.\n", hr);
+ if (FAILED(hr)) break;
+ if (hr == S_OK)
+ {
+ if (last_pts >= 0 && pts < last_pts) ++loops;
+ last_pts = pts;
+ }
+ Sleep(1);
+ }
+ ok(loops == 2, "Video did not restart twice, observed %u loops.\n", loops);
+ ok(!IMFMediaEngineEx_IsEnded(media_engine), "Looping playback reported ended.\n");
+
+ /* Turning looping off must let the current iteration complete normally. */
+ hr = IMFMediaEngineEx_SetLoop(media_engine, FALSE);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ start = GetTickCount();
+ while (!IMFMediaEngineEx_IsEnded(media_engine) && GetTickCount() - start < 5000)
+ {
+ hr = IMFMediaEngineEx_OnVideoStreamTick(media_engine, &pts);
+ ok(hr == S_OK || hr == S_FALSE, "Unexpected tick hr %#lx.\n", hr);
+ if (FAILED(hr)) break;
+ Sleep(1);
+ }
+ ok(IMFMediaEngineEx_IsEnded(media_engine), "Playback did not end after disabling looping.\n");
+
+done:
+ if (media_engine)
+ {
+ hr = IMFMediaEngineEx_Shutdown(media_engine);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ IMFMediaEngineEx_Release(media_engine);
+ }
+ if (stream) IMFByteStream_Release(stream);
+ SysFreeString(url);
+ IMFMediaEngineNotify_Release(&notify->IMFMediaEngineNotify_iface);
+}
+
START_TEST(mfmediaengine)
{
HRESULT hr;
@@ -3027,6 +3099,7 @@ START_TEST(mfmediaengine)
test_GetSeekable(TRUE);
test_media_extension();
test_SetCurrentTime();
+ test_looped_playback();
IMFMediaEngineClassFactory_Release(factory);
@@ -0,0 +1,56 @@
From: GloriousEggroll <[email protected]>
Date: Mon, 12 Oct 2026 10:00:00 -0600
Subject: [PATCH] mmdevapi: Preserve shared Sony endpoint properties on startup.
Patch 0019 avoids preloading persisted Sony endpoints so that current driver
enumeration, rather than stale registry records, determines which controllers
are present. However, it also deletes the endpoint's MMDevices registry key.
This runs in every process loading mmdevapi, while the registry is shared.
A second process can therefore delete the properties of an endpoint that the
game has already enumerated and still holds in its process-local device list.
The R2 trace in issue #750 shows this failure in Arknights: Endfield:
468691.227: PID 0140 opens the controller endpoint
{1bd72244-d350-40a7-a443-4f2e33b20d56}.
468694.168-173: PID 0474 loads mmdevapi and removes three persisted
DualSense endpoints from the shared registry.
468706.622 onward: PID 0140 repeatedly fails to read the original
endpoint's container ID and device format with error 2.
Replugging publishes a fresh endpoint and allows audio haptics to work. The
initial streams were stopped and released before the registry deletion, so
this change addresses the broken rediscovery/retry path, not the application's
reason for closing those first streams. Absence of the one-second PCM peak
trace on those short-lived streams is not evidence of a stalled audio pump.
Skip persisted Sony endpoints without deleting their shared property stores.
They remain absent from this process's collection until live driver discovery
publishes them, preserving the purpose of 0019. Keep normal-device preloading,
physical hotplug cleanup, routing, controller identity, and Steam Input
selection unchanged. The registry enumeration index must advance normally
because no key is removed from the enumeration.
Bug: https://github.com/GloriousEggroll/proton-ge-custom/issues/750#issuecomment-6126806805
---
dlls/mmdevapi/devenum.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/dlls/mmdevapi/devenum.c b/dlls/mmdevapi/devenum.c
--- a/dlls/mmdevapi/devenum.c
+++ b/dlls/mmdevapi/devenum.c
@@ -2012,9 +2012,11 @@
{
if (is_dualsense_endpoint_name( pv.pwszVal ))
{
- TRACE("Removing persisted DualSense endpoint %s.\n", debugstr_w(pv.pwszVal));
- if (RegDeleteTreeW(cur, guidvalue) == ERROR_SUCCESS)
- i--;
+ /* A different process may still use this shared property store.
+ * Skip the cached endpoint locally; only live driver discovery
+ * should publish it in this process. */
+ TRACE("Skipping persisted Sony endpoint %s (%s); keeping shared properties.\n",
+ debugstr_w(pv.pwszVal), debugstr_guid(&guid));
}
else
MMDevice_Create(pv.pwszVal, &guid, curflow,
+18
View File
@@ -35,6 +35,10 @@ apply_all_in_dir() {
# Swapchain colorspace instance-extension support is upstream in DXVK 3c40ca1b.
# Do not advertise HDR when Wine reports an SDR output, even with DXVK_HDR=1.
apply_patch "../patches/dxvk/dxgi-respect-output-hdr-state.patch"
# Query the active output by default instead of requiring an HDR opt-in.
apply_patch "../patches/dxvk/dxgi-auto-detect-hdr.patch"
# StarCraft II: avoid GPU hangs with D3D9 constant buffer streaming (#790).
apply_patch "../patches/dxvk/starcraft2-disable-constant-buffer-streaming.patch"
popd
pushd vkd3d-proton
@@ -151,6 +155,7 @@ apply_all_in_dir() {
-W dbghelp-Debug_Symbols \
-W version-VerQueryValue \
-W mf_http_support \
-W ntdll-Serial_Port_Detection \
-W server-PeekMessage \
-W msxml3-FreeThreadedXMLHTTP60 \
-W ntdll-ForceBottomUpAlloc \
@@ -185,6 +190,10 @@ apply_all_in_dir() {
# dbghelp-Debug_Symbols - Ubisoft Connect games (3/3 I had installed and could test) will crash inside pe_load_debug_info function with this enabled
# version-VerQueryValue - just a test and doesn't apply cleanly. not relevant for gaming
# mf_http_support - disabled in favor of custom ffmpeg backend video playback solution
# ntdll-Serial_Port_Detection - its mountmgr probe opens every /dev/ttyS*, ttyUSB*, ttyACM* without O_NOCTTY
# from winedevice.exe (a session leader without a terminal): the first one becomes its controlling tty and
# is hung up when the prefix shuts down, breaking the program that had it open (e.g. a Sinden lightgun's
# serial link to its driver: the gun stops working after each game)
# server-PeekMessage - already applied
# msxml3-FreeThreadedXMLHTTP60 - already applied
@@ -366,6 +375,9 @@ apply_all_in_dir() {
echo "WINE: -HOTFIX- Fall back when GnuTLS lacks NO_SHUFFLE_EXTENSIONS"
apply_patch "../patches/wine-hotfixes/pending/secur32-fallback-without-no-shuffle-extensions.patch"
echo "WINE: -HOTFIX- Backport schannel SECBUFFER_MISSING size fix for incomplete TLS records (upstream 9aee0e71, 35b1e7eb)"
apply_patch "../patches/wine-hotfixes/pending/secur32-schannel-fix-incomplete-message-missing-size.patch"
echo "WINE: -HOTFIX- Preserve driver-reported OpenGL GPU identity"
apply_patch "../patches/wine-hotfixes/pending/wined3d-preserve-runtime-opengl-gpu-description.patch"
@@ -375,6 +387,12 @@ apply_all_in_dir() {
echo "WINE: -HOTFIX- Keep Forza background windows unmapped on wlroots"
apply_patch "../patches/wine-hotfixes/pending/winex11-keep-forza-background-windows-unmapped-on-wlroots.patch"
echo "WINE: -HOTFIX- Reconcile unsupported minimization on xwayland-satellite"
apply_patch "../patches/wine-hotfixes/pending/winex11-handle-unsupported-minimization-on-satellite.patch"
echo "WINE: -HOTFIX- Do not paint off-desktop minimized frames into retained host windows"
apply_patch "../patches/wine-hotfixes/pending/server-clip-off-desktop-minimized-window-paint.patch"
echo "WINE: -HOTFIX- Share selected cursor images across processes"
apply_patch "../patches/wine-hotfixes/pending/win32u-share-selected-cursors-across-processes.patch"
@@ -0,0 +1,186 @@
From: GloriousEggroll <[email protected]>
Date: Sun, 11 Oct 2026 12:00:00 -0600
Subject: [PATCH] winewayland: Keep windows recoverable without host minimization.
Deadlock can minimize its window during startup on Niri before creating
its Vulkan surface. Niri advertises maximize and fullscreen capabilities,
but not minimize. The capability guard in the minimized-toplevel check
then lets the offscreen Win32 iconic rectangle destroy the xdg_toplevel.
The resulting keyboard leave cannot restore a window which is no longer
mapped. The same sequence occurs with the Steam overlay disabled.
Keep established, visible minimized toplevels mapped on these compositors
as well. Preserve their last usable geometry, decorations and input region
instead of applying the iconic rectangle. Do not send unsupported minimize
requests or change fullscreen state, but continue processing compositor
configures. Explicit hides, destruction and non-toplevel windows retain
their existing behavior.
Queue the existing window-state handler on the unsupported transition.
Restore only when the toplevel actually has Wayland keyboard focus, even
if Wine's foreground HWND has not changed. Otherwise keep the window
available for the user to focus and restore later. Do not reposition its
iconic Win32 rectangle from an output-enter notification in the meantime.
Unknown capabilities and compositors which support minimization retain
their existing minimize/restore behavior.
Complements the renderer preservation in fixup 0067. The original
minimized-toplevel implementation was authored by
Erhan Bilgili <[email protected]>:
https://github.com/nanomatters/wine-salkim/commit/7d3c16911a81d263c5f7a4769a8d57d17b3f1151
Bug: https://github.com/GloriousEggroll/proton-ge-custom/issues/762
---
diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c
--- a/dlls/winewayland.drv/wayland_surface.c
+++ b/dlls/winewayland.drv/wayland_surface.c
@@ -156,6 +156,10 @@
{
HRGN shape_region, owned_shape_region = 0;
+ /* Retain a focusable host window when the compositor cannot minimize it. */
+ if (wayland_surface_is_toplevel(surface) && surface->window.minimized &&
+ !wayland_surface_supports_minimize(surface)) return;
+
if (window_surface && window_surface->shape_region)
shape_region = window_surface->shape_region;
else
@@ -1181,7 +1185,7 @@
if (!(cap & WAYLAND_SURFACE_WM_CAPS_MAXIMIZE))
WARN("Compositor does not support maximize!\n");
if (!(cap & WAYLAND_SURFACE_WM_CAPS_MINIMIZE))
- WARN("Compositor does not support minimize, cannot implement window focus loss!\n");
+ TRACE("Compositor does not support minimize, keeping minimized toplevels mapped.\n");
if (!(data = wayland_win_data_get(hwnd))) return;
diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h
--- a/dlls/winewayland.drv/waylanddrv.h
+++ b/dlls/winewayland.drv/waylanddrv.h
@@ -649,6 +649,18 @@
return surface->role == WAYLAND_SURFACE_ROLE_TOPLEVEL && surface->xdg_toplevel;
}
+static inline BOOL wayland_surface_supports_minimize(const struct wayland_surface *surface)
+{
+ enum wayland_surface_wm_caps caps = surface->pending.caps;
+
+ /* Capabilities may arrive before the configure carrying them is applied. */
+ if (!caps) caps = surface->requested.caps;
+ if (!caps) caps = surface->processing.caps;
+ if (!caps) caps = surface->current.caps;
+
+ return !caps || (caps & WAYLAND_SURFACE_WM_CAPS_MINIMIZE);
+}
+
static inline BOOL wayland_surface_is_popup(struct wayland_surface *surface)
{
return surface->role == WAYLAND_SURFACE_ROLE_POPUP && surface->xdg_popup;
diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c
--- a/dlls/winewayland.drv/window.c
+++ b/dlls/winewayland.drv/window.c
@@ -298,10 +298,7 @@
DWORD style)
{
if (!surface || surface->role != WAYLAND_SURFACE_ROLE_TOPLEVEL) return FALSE;
- if (!(style & WS_MINIMIZE)) return FALSE;
-
- return !surface->current.caps ||
- (surface->current.caps & WAYLAND_SURFACE_WM_CAPS_MINIMIZE);
+ return !!(style & WS_MINIMIZE);
}
static BOOL is_menu_popup_candidate_style(DWORD style, DWORD exstyle)
@@ -363,7 +360,7 @@
struct wayland_client_surface *client = data->client_surface;
struct wayland_surface *surface;
enum wayland_surface_role role;
- BOOL visible, layer_set, server_decor = FALSE;
+ BOOL visible, layer_set, keep_minimized_geometry, server_decor = FALSE;
DWORD exstyle = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE);
DWORD style = NtUserGetWindowLongW(data->hwnd, GWL_STYLE);
@@ -438,7 +435,22 @@
}
surface->window.visible = visible;
- wayland_win_data_get_config(data, &surface->window);
+ keep_minimized_geometry = role == WAYLAND_SURFACE_ROLE_TOPLEVEL &&
+ should_keep_minimized_toplevel_mapped(surface, style) &&
+ !wayland_surface_supports_minimize(surface);
+ if (keep_minimized_geometry)
+ {
+ /* Without host minimization, the existing window is the only way to
+ * focus and restore the app. Do not replace its usable geometry with
+ * Win32's offscreen iconic rectangle or empty client/input region. */
+ if (!surface->window.minimized)
+ {
+ TRACE("hwnd=%p keeping minimized toplevel without host minimize support\n", data->hwnd);
+ NtUserPostMessage(data->hwnd, WM_WINE_WINDOW_STATE_CHANGED, 0, 0);
+ }
+ surface->window.minimized = TRUE;
+ }
+ else wayland_win_data_get_config(data, &surface->window);
/* If the window is a visible toplevel make it a wayland
* xdg_toplevel. Otherwise keep it role-less to avoid polluting the
@@ -459,7 +471,7 @@
stack_below_parent);
break;
case WAYLAND_SURFACE_ROLE_TOPLEVEL:
- wayland_surface_make_toplevel(surface, server_decor);
+ if (!keep_minimized_geometry) wayland_surface_make_toplevel(surface, server_decor);
break;
}
@@ -495,6 +507,9 @@
processing_config ? "processing" : "current",
processing_config ? surface->processing.state : surface->current.state);
+ if (!processing_config && surface->window.minimized &&
+ !wayland_surface_supports_minimize(surface)) return;
+
/* update the parent here as well to ensure that its not stale if the owner is updated
* with no new contents comitted or state change */
wayland_surface_update_toplevel_parent(surface);
@@ -1643,7 +1658,7 @@
struct wayland_win_data *data;
DWORD style;
HWND focused_hwnd, old_foreground;
- BOOL ret;
+ BOOL ret, host_minimize_supported = TRUE;
/* win32u uses a second call with NULL outputs to release any host state
* lock acquired by the first call. Wine-Wayland does not acquire one. */
@@ -1670,6 +1685,9 @@
SetRectEmpty(&data->state_update_rect);
data->state_update_foreground = NULL;
+ if ((surface = data->wayland_surface) && wayland_surface_is_toplevel(surface))
+ host_minimize_supported = wayland_surface_supports_minimize(surface);
+
wayland_win_data_release(data);
/* in these cases we dont need to update the window focus, borrowed from winemac */
@@ -1689,8 +1707,11 @@
if (!*foreground && old_foreground != focused_hwnd) *foreground = focused_hwnd;
/* we can't track if the host window is minimized or unminimized, but
- * if we have keyboard focus on this window we can treat it as restored. */
- if (!*state_cmd && (style & WS_MINIMIZE) && focused_hwnd == hwnd && old_foreground != hwnd)
+ * if we have keyboard focus on this window we can treat it as restored.
+ * An unsupported minimize may never have changed the host keyboard focus,
+ * so do not require a foreground transition in that case. */
+ if (!*state_cmd && (style & WS_MINIMIZE) && focused_hwnd == hwnd &&
+ (old_foreground != hwnd || !host_minimize_supported))
*state_cmd = MAKELONG(SC_RESTORE, 1);
else if (!*state_cmd && !*swp_flags && (data = wayland_win_data_get(hwnd)))
{
@@ -1698,6 +1719,7 @@
if (!(surface = data->wayland_surface)) goto skip;
if (!wayland_surface_is_toplevel(surface)) goto skip;
+ if ((style & WS_MINIMIZE) && !host_minimize_supported) goto skip;
if (wl_list_empty(&surface->output_list)) goto skip;
/* the output hint syncs the win32u position to the toplevel position */
if (surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) goto skip;
@@ -0,0 +1,140 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] wine: Detect Gamescope HDR and honor HDR opt-outs.
Native Wayland already reports the compositor's per-output HDR state.
X11 still reports HDR solely from DXVK_HDR, requiring an opt-in even
when Gamescope can describe the current HDR path.
Read GAMESCOPE_HDR_OUTPUT_FEEDBACK from the X root, the same property
used by Gamescope's WSI layer. Validate its type, format, and length.
A reported SDR state takes precedence over an inherited DXVK_HDR=1.
Refresh display state when the desktop thread receives feedback changes.
Without feedback, default to SDR but preserve an explicit DXVK_HDR=1
for older HDR WSI implementations. This does not infer enabled HDR from
EDID capability, the presence of a Wayland session, or another monitor.
Honor DXVK_HDR=0 and DXVK_NO_HDR=1 in X11 and native Wayland. Apply the
Wayland opt-out before generating HDR EDID metadata.
Together with dxgi-auto-detect-hdr.patch, this removes the need for the
Proton HDR launch flags without advertising HDR on unknown SDR paths.
No compositor configuration or graphics-driver selection is changed.
References:
https://github.com/ValveSoftware/gamescope/blob/master/src/steamcompmgr.cpp
https://github.com/ValveSoftware/gamescope/blob/master/layer/VkLayer_FROG_gamescope_wsi.cpp
Manual validation after rebuilding:
- Gamescope HDR enabled/disabled, both without DXVK_HDR.
- Change Gamescope HDR state while the application is open.
- Ordinary X11/XWayland without feedback defaults to SDR.
- Explicit HDR disable on both Wine drivers.
- Existing Wayland output detection and legacy X11 HDR opt-in.
---
diff --git a/dlls/winex11.drv/display.c b/dlls/winex11.drv/display.c
--- a/dlls/winex11.drv/display.c
+++ b/dlls/winex11.drv/display.c
@@ -482,6 +482,33 @@
return !!host_handler.register_event_handlers;
}
+static BOOL is_hdr_enabled(void)
+{
+ unsigned long count, remaining;
+ unsigned char *data = NULL;
+ BOOL enabled, valid;
+ const char *env;
+ Atom type;
+ int format;
+
+ if ((env = getenv("DXVK_NO_HDR")) && *env == '1') return FALSE;
+ if ((env = getenv("DXVK_HDR")) && *env == '0') return FALSE;
+
+ /* Gamescope publishes this on each of its Xwayland roots. A known SDR
+ * result must win over the legacy HDR opt-in inherited from the session. */
+ valid = !XGetWindowProperty( gdi_display, DefaultRootWindow(gdi_display),
+ x11drv_atom(GAMESCOPE_HDR_OUTPUT_FEEDBACK), 0, 1, False,
+ XA_CARDINAL, &type, &format, &count, &remaining, &data );
+ valid = valid && type == XA_CARDINAL && format == 32 && count == 1 && !remaining;
+ enabled = valid && *(unsigned long *)data != 0;
+ if (data) XFree( data );
+ if (valid) return enabled;
+
+ /* Preserve explicit opt-in for older HDR WSI implementations. Unknown
+ * X11/Xwayland outputs remain SDR when no override was requested. */
+ return (env = getenv("DXVK_HDR")) && *env == '1';
+}
+
UINT X11DRV_UpdateDisplayDevices( const struct gdi_device_manager *device_manager, void *param )
{
INT gpu_count, adapter_count, monitor_count, current_adapter_count = 0;
@@ -490,13 +517,12 @@
struct x11drv_gpu *gpus;
INT gpu, adapter, monitor;
struct x11drv_mode *modes;
- const char *env;
BOOL hdr_enabled;
UINT mode_count;
TRACE( "via %s\n", debugstr_a(host_handler.name) );
- hdr_enabled = (env = getenv("DXVK_HDR")) && *env == '1';
+ hdr_enabled = is_hdr_enabled();
TRACE( "hdr_enabled %d.\n", hdr_enabled );
/* Initialize GPUs */
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c
--- a/dlls/winex11.drv/event.c
+++ b/dlls/winex11.drv/event.c
@@ -1497,6 +1497,10 @@
if (event->atom == x11drv_atom(_NET_SUPPORTED)) handle_net_supported_notify( event );
if (event->atom == x11drv_atom(_NET_ACTIVE_WINDOW)) handle_net_active_window( event );
if (event->atom == x11drv_atom(_NET_SUPPORTING_WM_CHECK)) handle_net_supporting_wm_check_notify( event );
+ if (event->atom == x11drv_atom(GAMESCOPE_HDR_OUTPUT_FEEDBACK) &&
+ event->window == DefaultRootWindow(event->display) && hwnd == NtUserGetDesktopWindow() &&
+ NtUserGetWindowThread( hwnd, NULL ) == GetCurrentThreadId())
+ NtUserCallNoParam( NtUserCallNoParam_DisplayModeChanged );
return TRUE;
}
diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h
--- a/dlls/winex11.drv/x11drv.h
+++ b/dlls/winex11.drv/x11drv.h
@@ -598,6 +598,7 @@
XATOM_text_uri_list,
XATOM_GAMESCOPE_XALIA_OVERLAY,
XATOM_GAMESCOPE_DISPLAY_EDID_PATH,
+ XATOM_GAMESCOPE_HDR_OUTPUT_FEEDBACK,
NB_XATOMS
};
diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c
--- a/dlls/winex11.drv/x11drv_main.c
+++ b/dlls/winex11.drv/x11drv_main.c
@@ -210,6 +210,7 @@
"text/uri-list",
"GAMESCOPE_XALIA_OVERLAY",
"GAMESCOPE_DISPLAY_EDID_PATH",
+ "GAMESCOPE_HDR_OUTPUT_FEEDBACK",
};
/***********************************************************************
diff --git a/dlls/winewayland.drv/display.c b/dlls/winewayland.drv/display.c
--- a/dlls/winewayland.drv/display.c
+++ b/dlls/winewayland.drv/display.c
@@ -385,6 +385,8 @@
/* An HDR request cannot enable HDR on an SDR compositor output. */
if ((env = getenv("DXVK_NO_HDR")) && *env == '1')
monitor.hdr_enabled = FALSE;
+ if ((env = getenv("DXVK_HDR")) && *env == '0')
+ monitor.hdr_enabled = FALSE;
info->flags = MONITOR_INFO_HAS_PRIMARIES | MONITOR_INFO_HAS_PREFERRED_MODE
| MONITOR_INFO_HAS_PHYSICAL_DIMENSIONS | MONITOR_INFO_HAS_MONITOR_NAME;
@@ -0,0 +1,71 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] winewayland: Detect HDR independently of Windows image descriptions.
Dragon's Dogma 2 no longer exposes HDR after 58448e1e added DXGI checks
against Wine's active output state. Replacing DXVK with the GE-Proton11-7
release version avoids the check, but does not correct the output state.
The em11 color-management code requires supports_win_scrgb before marking
an output as HDR. These Windows-specific image descriptions are optional:
the Vulkan driver can present HDR10 through standard Wayland parametric
color management without either Windows-specific feature.
On the affected labwc session, both outputs advertise BT.2020/PQ with
max_lum=10000 and ref_lum=203, and RADV exposes HDR10_ST2084 on Wayland
surfaces. Neither Windows image description is advertised, so Wine
incorrectly reports SDR and DXGI rejects HDR before presentation.
Derive output HDR state from its reported luminance headroom, independently
of the optional presentation path. Keep Windows image-description support
as the gate for Wine's passthrough mapping in vulkan.c; otherwise the
original colorspace is passed to Vulkan WSI. Surface-format support checks,
per-output SDR fallback, and explicit HDR opt-outs remain unchanged.
Downgrade the missing optional-feature messages to traces: their absence
does not imply broken HDR when Vulkan WSI supports the requested colorspace.
Follow-up to the color-management implementation by Etaash Mathamsetty
<[email protected]>.
Validation before rebuilding:
- wayland-info: HDR output luminances, no windows_scrgb/windows_bt2100.
- vulkaninfo: RADV exposes HDR10_ST2084 for VK_KHR_wayland_surface.
Manual validation needed after rebuilding:
- Dragon's Dogma 2 with native Wayland and HDR enabled, without HDR flags.
- HDR disabled and mixed SDR/HDR outputs must retain SDR fallback.
- DXVK_NO_HDR=1 and DXVK_HDR=0 must still disable HDR reporting.
- Compositors with Windows image descriptions retain passthrough mapping.
---
diff --git a/dlls/winewayland.drv/wayland_output.c b/dlls/winewayland.drv/wayland_output.c
--- a/dlls/winewayland.drv/wayland_output.c
+++ b/dlls/winewayland.drv/wayland_output.c
@@ -196,8 +196,9 @@ static void wayland_output_done(struct wayland_output *output)
output->current.ref_lum = output->pending.ref_lum;
}
- output->current.supports_hdr = process_wayland.supports_win_scrgb &&
- (output->current.max_lum > output->current.ref_lum);
+ /* Output HDR state is independent of the optional Windows image descriptions.
+ * Vulkan WSI can handle HDR colorspaces through standard color management. */
+ output->current.supports_hdr = output->current.max_lum > output->current.ref_lum;
output->current.flags |= output->pending.flags;
output->pending.flags = 0;
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c
--- a/dlls/winewayland.drv/wayland.c
+++ b/dlls/winewayland.drv/wayland.c
@@ -745,10 +745,10 @@ BOOL wayland_process_init(void)
ERR("Wayland compositor doesn't support xdg_activation_v1! (Window Activation will not be supported)\n");
if (!process_wayland.supports_win_pq)
- ERR("Wayland compositor doesn't expose windows_bt2100 image description (HDR may look broken)!\n");
+ TRACE("No windows_bt2100 image description; using Vulkan WSI for HDR10.\n");
if (!process_wayland.supports_win_scrgb)
- ERR("Wayland compositor doesn't expose windows_scrgb image description (HDR may look broken)!\n");
+ TRACE("No windows_scrgb image description; using Vulkan WSI for scRGB.\n");
if (!process_wayland.zwp_linux_dmabuf_v1)
WARN("Wayland compositor doesn't support optional zwp_linux_dmabuf_v1 (HWND dmabuf forwarding won't work)\n");
@@ -0,0 +1,98 @@
From 9aee0e713e004256419e3a253a3634364a104e18 Mon Sep 17 00:00:00 2001
From: Nikolay Sivov <[email protected]>
Date: Tue, 24 Mar 2026 21:14:15 +0100
Subject: [PATCH] secur32/tests: Test required buffer size in case of
SEC_E_INCOMPLETE_MESSAGE.
Required remaining size is returned for both SECBUFFER_DATA and the first SECBUFFER_EMPTY buffer.
Signed-off-by: Nikolay Sivov <[email protected]>
---
dlls/secur32/tests/schannel.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/dlls/secur32/tests/schannel.c b/dlls/secur32/tests/schannel.c
index 2f693c6f871..66e995ba21c 100644
--- a/dlls/secur32/tests/schannel.c
+++ b/dlls/secur32/tests/schannel.c
@@ -1371,11 +1371,15 @@ static void test_communication(void)
/* Incomplete data */
buffers[0].pBuffers[1].BufferType = SECBUFFER_EMPTY;
+ buffers[0].pBuffers[1].cbBuffer = 1;
buffers[0].pBuffers[0].cbBuffer = (data[3]<<8) | data[4];
status = DecryptMessage(&context, &buffers[0], 0, NULL);
ok(status == SEC_E_INCOMPLETE_MESSAGE, "Expected SEC_E_INCOMPLETE_MESSAGE, got %08lx\n", status);
ok(buffers[0].pBuffers[0].BufferType == SECBUFFER_MISSING, "Expected first buffer to be SECBUFFER_MISSING\n");
ok(buffers[0].pBuffers[0].cbBuffer == 5, "Expected first buffer to be a five bytes\n");
+ ok(buffers[0].pBuffers[1].BufferType == SECBUFFER_MISSING, "Unexpected buffer type\n");
+ todo_wine
+ ok(buffers[0].pBuffers[1].cbBuffer == 5, "Unexpected buffer size\n");
/* Renegotiate */
buffers[0].pBuffers[0].BufferType = SECBUFFER_DATA;
--
GitLab
From 35b1e7eb9a7e66c49fac8583e50186c6c21736e6 Mon Sep 17 00:00:00 2001
From: Nikolay Sivov <[email protected]>
Date: Tue, 24 Mar 2026 22:11:38 +0100
Subject: [PATCH] secur32/schannel: Fix returned required buffer size for
incomplete messages in DecryptMessage().
Signed-off-by: Nikolay Sivov <[email protected]>
---
dlls/secur32/schannel.c | 6 +++---
dlls/secur32/tests/schannel.c | 1 -
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/dlls/secur32/schannel.c b/dlls/secur32/schannel.c
index 3ad6be5e738..83aea0b4737 100644
--- a/dlls/secur32/schannel.c
+++ b/dlls/secur32/schannel.c
@@ -1486,13 +1486,13 @@ static void schan_decrypt_fill_buffer(PSecBufferDesc message, ULONG buffer_type,
static SECURITY_STATUS SEC_ENTRY schan_DecryptMessage(PCtxtHandle context_handle,
PSecBufferDesc message, ULONG message_seq_no, PULONG quality)
{
+ unsigned expected_size, remaining_size;
SECURITY_STATUS status = SEC_E_OK;
struct schan_context *ctx;
struct recv_params params;
SecBuffer *buffer;
SIZE_T data_size;
char *data;
- unsigned expected_size;
ULONG received = 0;
int idx;
unsigned char *buf_ptr;
@@ -1517,13 +1517,13 @@ static SECURITY_STATUS SEC_ENTRY schan_DecryptMessage(PCtxtHandle context_handle
{
TRACE("Expected %u bytes, but buffer only contains %lu bytes\n", expected_size, buffer->cbBuffer);
buffer->BufferType = SECBUFFER_MISSING;
- buffer->cbBuffer = expected_size - buffer->cbBuffer;
+ buffer->cbBuffer = remaining_size = expected_size - buffer->cbBuffer;
/* This is a bit weird, but windows does it too */
idx = schan_find_sec_buffer_idx(message, 0, SECBUFFER_EMPTY);
buffer = &message->pBuffers[idx];
buffer->BufferType = SECBUFFER_MISSING;
- buffer->cbBuffer = expected_size - buffer->cbBuffer;
+ buffer->cbBuffer = remaining_size;
TRACE("Returning SEC_E_INCOMPLETE_MESSAGE\n");
return SEC_E_INCOMPLETE_MESSAGE;
diff --git a/dlls/secur32/tests/schannel.c b/dlls/secur32/tests/schannel.c
index 66e995ba21c..04ef9ca6880 100644
--- a/dlls/secur32/tests/schannel.c
+++ b/dlls/secur32/tests/schannel.c
@@ -1378,7 +1378,6 @@ static void test_communication(void)
ok(buffers[0].pBuffers[0].BufferType == SECBUFFER_MISSING, "Expected first buffer to be SECBUFFER_MISSING\n");
ok(buffers[0].pBuffers[0].cbBuffer == 5, "Expected first buffer to be a five bytes\n");
ok(buffers[0].pBuffers[1].BufferType == SECBUFFER_MISSING, "Unexpected buffer type\n");
- todo_wine
ok(buffers[0].pBuffers[1].cbBuffer == 5, "Unexpected buffer size\n");
/* Renegotiate */
--
GitLab
@@ -0,0 +1,127 @@
From: GloriousEggroll <[email protected]>
Date: Tue, 13 Oct 2026 01:00:00 -0600
Subject: [PATCH] server: Clip painting of off-desktop minimized windows.
Deadlock briefly paints a white 160x31 rectangle at startup on Niri,
even after unsupported minimization is reconciled and with Steam's
overlay disabled. The existing X11 traces show SW_MINIMIZE parking the
Win32 window at (-32000,-32000) with exactly that iconic size, followed
by non-client GDI painting into the still-mapped full-size X drawable.
Top-level visible regions normally rely on the native window manager
for clipping. That assumption fails when the host does not implement
minimization and keeps the normal-sized window mapped while Wine waits
to restore its Win32 state.
Return an empty GDI visible region for minimized top-level windows whose
iconic rectangle lies wholly outside the desktop. Map the desktop bounds
to the window's DPI before comparing them. Keep child windows, on-screen
minimized icons (including virtual desktops), and non-minimized windows
on the existing path. Normal positioning invalidates DCs and restores
their visible region when the window is restored.
Do this in the shared server path so buffered GDI painting and direct
X11 drawing are clipped consistently, including Wine-Wayland. Do not
change Vulkan surface lifetime, host mapping, or focus reconciliation.
Keep the existing Wine-Wayland 0068 and satellite minimization fixes.
Add coverage for cached and own DCs across minimize/restore, including
the non-client DC used for the iconic frame. Skip the off-desktop checks
when the host restores the window or displays its minimized icon.
Bug: https://github.com/GloriousEggroll/proton-ge-custom/issues/762
---
diff --git a/server/window.c b/server/window.c
--- a/server/window.c
+++ b/server/window.c
@@ -1453,6 +1453,17 @@
return region;
}
+ /* Native window managers normally clip top-level windows, but may keep an
+ * unsupported minimize mapped. Do not paint its off-desktop iconic frame
+ * into the retained host window. On-screen minimized icons still paint. */
+ if ((win->style & WS_MINIMIZE) && is_desktop_window( win->parent ))
+ {
+ struct rectangle desktop = win->parent->client_rect;
+
+ map_dpi_rect( win, &desktop, get_window_dpi( win->parent ), get_window_dpi( win ) );
+ if (!intersect_rect( &desktop, &desktop, &win->visible_rect )) return region;
+ }
+
/* create a region relative to the window itself */
if ((flags & DCX_PARENTCLIP) && !is_desktop_window( win->parent ))
diff --git a/dlls/user32/tests/dce.c b/dlls/user32/tests/dce.c
--- a/dlls/user32/tests/dce.c
+++ b/dlls/user32/tests/dce.c
@@ -711,6 +711,61 @@
DestroyWindow(hwnd_cache_rtl);
}
+static void test_minimized_dc_visrgn(void)
+{
+ static const char *classes[] = { "cache_class", "owndc_class" };
+ RECT desktop, rect, intersect;
+ HWND hwnd;
+ HDC hdc, window_dc;
+ unsigned int i;
+ int ret;
+
+ SetRect( &desktop, GetSystemMetrics( SM_XVIRTUALSCREEN ), GetSystemMetrics( SM_YVIRTUALSCREEN ), 0, 0 );
+ desktop.right = desktop.left + GetSystemMetrics( SM_CXVIRTUALSCREEN );
+ desktop.bottom = desktop.top + GetSystemMetrics( SM_CYVIRTUALSCREEN );
+
+ for (i = 0; i < ARRAY_SIZE(classes); ++i)
+ {
+ winetest_push_context( "%s", classes[i] );
+ hwnd = CreateWindowA( classes[i], NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ 0, 0, 200, 150, 0, 0, GetModuleHandleA(0), NULL );
+ ok( !!hwnd, "CreateWindow failed, error %lu\n", GetLastError() );
+ if (!hwnd)
+ {
+ winetest_pop_context();
+ continue;
+ }
+
+ hdc = GetDC( hwnd );
+ ok( !!hdc, "GetDC failed\n" );
+ ShowWindow( hwnd, SW_SHOWMINNOACTIVE );
+ GetWindowRect( hwnd, &rect );
+ if (!IsIconic( hwnd ) || IntersectRect( &intersect, &desktop, &rect ))
+ skip( "Window manager did not park the minimized window outside the desktop\n" );
+ else
+ {
+ ret = GetClipBox( hdc, &rect );
+ ok( ret == NULLREGION, "Minimized client region %d, %s\n", ret, wine_dbgstr_rect(&rect) );
+ window_dc = GetWindowDC( hwnd );
+ ok( !!window_dc, "GetWindowDC failed\n" );
+ ret = GetClipBox( window_dc, &rect );
+ ok( ret == NULLREGION, "Minimized window region %d, %s\n", ret, wine_dbgstr_rect(&rect) );
+ ReleaseDC( hwnd, window_dc );
+ }
+
+ ShowWindow( hwnd, SW_RESTORE );
+ ret = GetClipBox( hdc, &rect );
+ ok( ret > NULLREGION, "Restored client region %d, %s\n", ret, wine_dbgstr_rect(&rect) );
+ ReleaseDC( hwnd, hdc );
+ window_dc = GetWindowDC( hwnd );
+ ret = GetClipBox( window_dc, &rect );
+ ok( ret > NULLREGION, "Restored window region %d, %s\n", ret, wine_dbgstr_rect(&rect) );
+ ReleaseDC( hwnd, window_dc );
+ DestroyWindow( hwnd );
+ winetest_pop_context();
+ }
+}
+
static void test_destroyed_window(void)
{
HDC dc, old_dc;
@@ -833,6 +888,7 @@
test_scroll_window();
test_invisible_create();
test_dc_layout();
+ test_minimized_dc_visrgn();
/* this should be last */
test_destroyed_window();
}
@@ -0,0 +1,139 @@
From: GloriousEggroll <[email protected]>
Date: Mon, 12 Oct 2026 01:00:00 -0600
Subject: [PATCH] winex11: Reconcile unsupported minimization on satellite.
Both X11 traces for Deadlock on Niri show the same startup transition,
with and without the user's window rule. The game calls SW_MINIMIZE
before creating its Vulkan surface. Wine requests NormalState ->
IconicState, but the window manager does not acknowledge that request.
The iconic Win32 client rectangle then produces a 1x1 Vulkan child.
Refocusing causes WM_STATE=NormalState to arrive, clears the pending
request and restores the child to 3440x1440.
xwayland-satellite does not handle the WM_CHANGE_STATE message sent by
XIconifyWindow. It writes NormalState on mapping and focusing instead:
https://github.com/Supreeeme/xwayland-satellite/blob/ae88928f7334556d298b8d9552abdf395931931b/src/xstate/mod.rs
First recognize its WM name: satellite writes _NET_WM_NAME as STRING
rather than UTF8_STRING. Request either string type and validate the
format; otherwise Xlib returns success with an empty buffer for the type
mismatch and Wine identifies the window manager as "".
For managed, non-embedded satellite windows without advertised HIDDEN
support, keep the desired host state NormalState rather than queueing an
unsupported iconify request. Post the normal window-state reconciliation
message, and repeat it if restoration had to wait for decoration hints.
Only restore the Win32 minimized state while the host reports the window
active, so a background game's focus-loss minimize cannot steal focus.
Do not infer unsupported minimization from absent EWMH capabilities on
other window managers: legacy ICCCM WMs can minimize without those hints.
Preserve hidden windows, embedded windows, virtual desktops, and the
existing paths for other WMs or satellite advertising HIDDEN support.
This complements Wine-Wayland fixup 0068; it does not replace it.
Bug: https://github.com/GloriousEggroll/proton-ge-custom/issues/762
Logs: https://github.com/GloriousEggroll/proton-ge-custom/issues/762#issuecomment-6116456428
---
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c
--- a/dlls/winex11.drv/window.c
+++ b/dlls/winex11.drv/window.c
@@ -1803,6 +1803,15 @@
return cached;
}
+static BOOL window_minimize_unsupported( struct x11drv_win_data *data )
+{
+ /* Satellite ignores WM_CHANGE_STATE. Do not infer this for other WMs from
+ * missing EWMH hints alone, since legacy ICCCM WMs can still minimize. */
+ return data->managed && !data->embedded && !is_virtual_desktop() &&
+ X11DRV_HasWindowManager( "xwayland-satellite" ) &&
+ !is_net_supported( x11drv_atom(_NET_WM_STATE_HIDDEN) );
+}
+
static void window_set_wm_state( struct x11drv_win_data *data, UINT new_state, BOOL activate )
{
UINT old_state = data->pending_state.wm_state;
@@ -1818,6 +1827,17 @@
activate = FALSE;
}
+ if (new_state == IconicState && window_minimize_unsupported( data ))
+ {
+ /* Keep the mapped host state instead of waiting for an acknowledgment
+ * that will never arrive. Reconcile Win32 state on the window thread. */
+ TRACE( "window %p/%lx, host minimization unsupported, keeping NormalState\n",
+ data->hwnd, data->whole_window );
+ new_state = NormalState;
+ activate = FALSE;
+ NtUserPostMessage( data->hwnd, WM_WINE_WINDOW_STATE_CHANGED, 0, 0 );
+ }
+
data->desired_state.wm_state = new_state;
data->desired_state.activate = activate;
if (data->state_locks) return; /* win32 state is being updated, delay the change */
@@ -1983,6 +2003,12 @@
if (data->mwm_hints_serial) return 0; /* another MWM_HINT update is pending, wait for it to complete */
if (data->configure_serial) return 0; /* another config update is pending, wait for it to complete */
+ /* An ignored minimize must not bring a background window back to the
+ * foreground. Satellite sends WM_STATE again when the user focuses it. */
+ if ((old_style & WS_MINIMIZE) && window_minimize_unsupported( data ) &&
+ get_net_active_window( data->display ) != data->whole_window)
+ return 0;
+
new_style = old_style & ~(WS_VISIBLE | WS_MINIMIZE | WS_MAXIMIZE);
if (data->current_state.wm_state != WithdrawnState) new_style |= WS_VISIBLE;
if (data->current_state.wm_state == IconicState) new_style |= WS_MINIMIZE;
@@ -2301,6 +2327,11 @@
/* send any pending changes from the desired state */
window_request_desired_state( data );
+
+ /* The unsupported-minimize restore may have waited for these hints. */
+ if ((NtUserGetWindowLongW( data->hwnd, GWL_STYLE ) & WS_MINIMIZE) &&
+ window_minimize_unsupported( data ))
+ NtUserPostMessage( data->hwnd, WM_WINE_WINDOW_STATE_CHANGED, 0, 0 );
}
void window_wm_normal_hints_notify( struct x11drv_win_data *data, unsigned long serial, const XSizeHints *value )
@@ -2393,9 +2424,15 @@
*name = NULL;
X11DRV_expect_error( display, host_window_error, NULL );
- ret = XGetWindowProperty( display, window, x11drv_atom(_NET_WM_NAME), 0, 65536 / sizeof(CARD32), False, x11drv_atom(UTF8_STRING),
+ /* Satellite supplies its ASCII WM name as STRING rather than UTF8_STRING.
+ * A type mismatch otherwise succeeds with an empty buffer from Xlib. */
+ ret = XGetWindowProperty( display, window, x11drv_atom(_NET_WM_NAME), 0, 65536 / sizeof(CARD32), False, AnyPropertyType,
&type, &format, &count, &remaining, (unsigned char **)name );
- return !X11DRV_check_error() && !ret && *name;
+ if (!X11DRV_check_error() && !ret && *name && format == 8 && count &&
+ (type == x11drv_atom(UTF8_STRING) || type == XA_STRING)) return TRUE;
+ XFree( *name );
+ *name = NULL;
+ return FALSE;
}
static BOOL get_window_wm_name( Display *display, Window window, char **name )
diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h
--- a/dlls/winex11.drv/x11drv.h
+++ b/dlls/winex11.drv/x11drv.h
@@ -545,6 +545,7 @@
XATOM__NET_WM_STATE_BELOW,
XATOM__NET_WM_STATE_DEMANDS_ATTENTION,
XATOM__NET_WM_STATE_FULLSCREEN,
+ XATOM__NET_WM_STATE_HIDDEN,
XATOM__NET_WM_STATE_MAXIMIZED_HORZ,
XATOM__NET_WM_STATE_MAXIMIZED_VERT,
XATOM__NET_WM_STATE_SKIP_PAGER,
diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c
--- a/dlls/winex11.drv/x11drv_main.c
+++ b/dlls/winex11.drv/x11drv_main.c
@@ -157,6 +157,7 @@
"_NET_WM_STATE_BELOW",
"_NET_WM_STATE_DEMANDS_ATTENTION",
"_NET_WM_STATE_FULLSCREEN",
+ "_NET_WM_STATE_HIDDEN",
"_NET_WM_STATE_MAXIMIZED_HORZ",
"_NET_WM_STATE_MAXIMIZED_VERT",
"_NET_WM_STATE_SKIP_PAGER",
@@ -0,0 +1,54 @@
From: GloriousEggroll <[email protected]>
Subject: [PATCH] mini: order publication and consumption of delegate trampoline caches.
The Ampere ARM64 build of Wine-Mono 11.3.0 crashes in Roslyn while compiling
mscorlib.dll. The saved core contains an Action for CompileNamedTypeAsync
with the optimized invoke_impl but a NULL method_ptr. The shared
MonoDelegateTrampInfo already contains both valid pointers. The ARM64
invoke stub loads method_ptr and branches to zero.
mono_delegate_trampoline publishes method_ptr followed by invoke_impl,
and the inlined constructor reads invoke_impl followed by method_ptr.
Neither side orders those accesses. On weakly ordered CPUs a constructor
can therefore copy the new invoke_impl together with the old NULL
method_ptr, permanently skipping lazy initialization for that delegate.
Publish the target before the optimized entry point with a write barrier,
and acquire the entry point before reading the target in the generated
constructor. Keep the invoke_impl-first read order: observing the old lazy
entry with a new target is safe, but the inverse is not. Also order the
target before publishing the optimized entry on the delegate itself.
This fixes the runtime protocol rather than disabling Roslyn parallelism
or changing the compiler or class-library profile. The existing prep loop
applies it to both the native host Mono and the Windows runtime builds.
---
diff --git a/mono/mini/mini-trampolines.c b/mono/mini/mini-trampolines.c
--- a/mono/mini/mini-trampolines.c
+++ b/mono/mini/mini-trampolines.c
@@ -1221,9 +1221,13 @@ mono_delegate_trampoline (host_mgreg_t *regs, guint8 *code, gpointer *arg, guint8* tramp)
code = (guint8 *)mini_add_method_trampoline (m, code, mono_method_needs_static_rgctx_invoke (m, FALSE), FALSE);
}
+ /* The optimized entry point uses method_ptr without lazy initialization. */
+ mono_memory_write_barrier ();
delegate->invoke_impl = mono_get_addr_from_ftnptr (code);
if (enable_caching && !callvirt && tramp_info->method) {
tramp_info->method_ptr = delegate->method_ptr;
+ /* Pair with the acquire in the inlined delegate constructor. */
+ mono_memory_write_barrier ();
tramp_info->invoke_impl = delegate->invoke_impl;
}
diff --git a/mono/mini/method-to-ir.c b/mono/mini/method-to-ir.c
--- a/mono/mini/method-to-ir.c
+++ b/mono/mini/method-to-ir.c
@@ -3699,6 +3699,8 @@ handle_delegate_ctor (MonoCompile *cfg, MonoClass *klass, MonoInst *target, MonoMethod *method, int target_method_context_used, int invoke_context_used, gboolean virtual_)
} else {
dreg = alloc_preg (cfg);
MONO_EMIT_NEW_LOAD_MEMBASE (cfg, dreg, tramp_ins->dreg, MONO_STRUCT_OFFSET (MonoDelegateTrampInfo, invoke_impl));
+ /* A cached optimized entry must not be paired with a stale method_ptr. */
+ mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STORE_MEMBASE_REG, obj->dreg, MONO_STRUCT_OFFSET (MonoDelegate, invoke_impl), dreg);
dreg = alloc_preg (cfg);
+42
View File
@@ -69,6 +69,48 @@ Source/metadata inspection, patch validation, and diagnostic launches of
the user's rebuilt runtime have been performed. The regression fixtures
have not been assembled or run.
## ARM64 Compiler Delegate Cache Race
`0004-mini-order-delegate-trampoline-cache-publication.patch` adds matching
publication/consumption barriers to the JIT delegate trampoline cache.
The 2026-10-13 Ampere `GE-Proton11-8-aarch64` build aborted with SIGSEGV
while Roslyn compiled `net_4_x-linux/mscorlib.dll` during the System.Drawing
bootstrap (`Makefile.in`'s drawing stamp, `mono.make:181`, error 134).
This is the native ARM64 host Mono, not an emulated Windows compiler.
Offline inspection of the saved core found an `Action` for Roslyn's
`<CompileNamedTypeAsync>b__0` with a valid optimized `invoke_impl` and a
NULL `method_ptr`. Its shared `MonoDelegateTrampInfo` had both valid pointers.
Disassembly confirmed that the constructor copies `invoke_impl` and then
`method_ptr` without an acquire barrier, while the runtime publishes those
fields without a release barrier. The optimized ARM64 invoke stub loads
the NULL target and branches to address zero. Ordering the cache accesses
prevents this mismatched snapshot; a barrier also precedes publication of
the delegate's own optimized entry point. No compiler parallelism, profile,
game overrides, or graphics code is changed.
The existing prep loop picks up patch 0004 automatically. Re-run prep before
the next build so the host runtime is rebuilt as well as the Windows DLLs.
Do not apply it only to the copied build tree: that tree is regenerated.
The core and source establish the missing synchronization; the fixed ARM64
build still requires validation by the user.
`tests/delegate-cache-publication.cs` stresses simultaneous construction
and invocation of instance, shared-generic, and static delegates, creating
fresh AppDomains to exercise cold caches. It is probabilistic and a single
successful run does not establish that an unpatched runtime is safe.
After building the patched host Mono, run it manually using that runtime
and its matching class libraries, for example from the Wine-Mono source root:
```sh
build/mono-unix/runtime/mono-wrapper mono/mcs/class/lib/net_4_x-linux/mcs.exe \
-out:/tmp/delegate-cache-publication.exe /path/to/patches/wine-mono/tests/delegate-cache-publication.cs
MONO_PATH="$PWD/mono/mcs/class/lib/net_4_x-linux" \
build/mono-unix/runtime/mono-wrapper /tmp/delegate-cache-publication.exe
```
The agent has not compiled or run this fixture, nor restarted the Ampere build.
## FNA Stencil Reference Ordering
`fna/0001-graphicsdevice-preserve-stencil-reference-order.patch` corrects
@@ -0,0 +1,107 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
// Each AppDomain has a cold trampoline cache; race construction against its
// first invocations. This is a stress test, not a deterministic race detector.
class DelegateCachePublication
{
const int WorkerCount = 16;
const int Iterations = 4096;
static int staticCalls;
sealed class Target<T>
{
public int Calls;
[MethodImpl(MethodImplOptions.NoInlining)]
void Invoke()
{
Calls++;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Action Create()
{
return Invoke;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void InvokeStatic()
{
Interlocked.Increment(ref staticCalls);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static Action CreateStatic()
{
return InvokeStatic;
}
static void RunRound()
{
Exception failure = null;
staticCalls = 0;
using (var ready = new CountdownEvent(WorkerCount))
using (var start = new ManualResetEvent(false))
{
var workers = new Thread[WorkerCount];
for (int i = 0; i < workers.Length; i++)
{
workers[i] = new Thread(() =>
{
var valueTarget = new Target<int>();
var sharedTarget = new Target<string>();
ready.Signal();
start.WaitOne();
try
{
for (int j = 0; j < Iterations; j++)
{
valueTarget.Create()();
sharedTarget.Create()();
CreateStatic()();
}
if (valueTarget.Calls != Iterations || sharedTarget.Calls != Iterations)
throw new Exception("A delegate invoked the wrong target.");
}
catch (Exception e)
{
Interlocked.CompareExchange(ref failure, e, null);
}
});
workers[i].Start();
}
ready.Wait();
start.Set();
foreach (var worker in workers)
worker.Join();
}
if (failure != null)
throw new Exception("Delegate cache stress failed.", failure);
if (staticCalls != WorkerCount * Iterations)
throw new Exception("A static delegate invocation was lost.");
}
static int Main(string[] args)
{
int rounds = args.Length == 0 ? 64 : int.Parse(args[0]);
if (rounds < 1)
throw new ArgumentOutOfRangeException("rounds");
for (int i = 0; i < rounds; i++)
{
var domain = AppDomain.CreateDomain("delegate-cache-" + i);
try
{
domain.DoCallBack(RunRound);
}
finally
{
AppDomain.Unload(domain);
}
}
Console.WriteLine("Delegate cache publication stress passed ({0} rounds).", rounds);
return 0;
}
}
+9 -1
View File
@@ -47,7 +47,7 @@ import protonfixes
#and edit it if needed.
import utilities
CURRENT_PREFIX_VERSION="GE-Proton11-7"
CURRENT_PREFIX_VERSION="GE-Proton11-9"
PFX="Proton: "
ld_path_var = "LD_LIBRARY_PATH"
@@ -2318,6 +2318,8 @@ class Session:
self.check_environment("PROTON_USE_OPTISCALER", "optiscaler")
self.check_environment("PROTON_NATIVE_AGS", "nativeags")
# Legacy overrides only; HDR detection no longer requires either flag.
# Preserve PROTON_USE_HDR precedence when both aliases are set.
self.check_environment("PROTON_ENABLE_HDR", "hdr")
self.check_environment("PROTON_USE_HDR", "hdr")
self.check_environment("PROTON_NO_NTSYNC", "nontsync")
@@ -2564,8 +2566,14 @@ class Session:
if "SDL_GAMECONTROLLER_IGNORE_DEVICES" in self.env:
del self.env["SDL_GAMECONTROLLER_IGNORE_DEVICES"]
# Leave DXVK_HDR unset by default: shared DXGI detects HDR for DX11/DX12
# from Wine Wayland output state or Gamescope XWayland feedback.
# Retain legacy opt-ins/opt-outs without changing the display driver.
if "hdr" in self.compat_config:
self.env["DXVK_HDR"] = "1"
elif "PROTON_ENABLE_HDR" in self.env or "PROTON_USE_HDR" in self.env:
self.env["DXVK_HDR"] = "0"
self.env["DXVK_NO_HDR"] = "1"
if "fnad3d11" in self.compat_config and "FNA3D_FORCE_DRIVER" not in self.env:
self.env["FNA3D_FORCE_DRIVER"] = "D3D11"