fix(vk): Linux/Wayland: take an sRGB surface format for the swapchain (#679 rebased) #1086

Merged
metroid98 merged 2 commits from fix/swapchain-srgb-rebase into main 2026-11-09 19:00:58 +00:00
metroid98 commented 2026-11-09 10:38:21 +00:00 (Migrated from github.com)

What

This is @t0masz407's #679 rebased onto current main, so that #1042 gets its fix. #679 was closed only because it conflicted ("please rebase on current main and reopen"), and it hasn't been reopened since. Tom's commit is kept as his own, with one small commit on top.

create_swapchain (gpu/vk/vk_main.cpp) kept the last B8G8R8A8_UNORM / R8G8B8A8_UNORM surface format, whatever its colour space. It now only takes VK_COLOR_SPACE_SRGB_NONLINEAR_KHR entries.

Why

Mesa's Wayland WSI (src/vulkan/wsi/wsi_common_wayland.c, wsi_wl_display_determine_colorspaces and wsi_wl_surface_get_formats on Mesa main) returns every format once per colour space: SRGB_NONLINEAR_KHR first, then PASS_THROUGH_EXT (always), then, when the compositor has wp_color_manager_v1 (KWin, Hyprland, ...), every colour space it can map. The last UNORM entry is therefore never sRGB on RADV + Wayland, and the compositor shows the picture unmanaged: the grey, washed-out image in #1042. That issue now has three reports, all AMD + Wayland: RX 6600, RX 9070 XT on Arch, RX 9070 XT on CachyOS. X11 and NVIDIA list only sRGB entries. On an RTX 3060 under KDE Wayland it does not reproduce (my data point in #1042).

Change from #679

#679 took the first sRGB match (break). The second commit keeps the old last-match rule, restricted to sRGB entries. On drivers that list only sRGB entries (NVIDIA, X11/XWayland, Windows), the chosen format is then exactly the one it is today, even when a driver lists both B8G8R8A8 and R8G8B8A8. The test (tests/test_swapchain_colorspace.py, picked up by tests/run_all.py, so the workflow edit from #679 is no longer needed) now checks for the sRGB condition and that the loop does not stop at the first match.

Testing

Tom tested the original on an RX 7900 XTX (RADV, Mesa 26.2.6, Hyprland HDR), see #679. I have no AMD card to test the rebase on, and I didn't build or run it locally: the conflict was only the neighbouring present-mode code, which keeps main's version. A test from someone on AMD + Wayland in #1042 would confirm it.

Fixes #1042

AI-assisted (Claude Code) on behalf of metroid98: rebase, Mesa source check and write-up; not run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_015DmSMGJSY9hHswALy4eqtQ

## What This is @t0masz407's #679 rebased onto current main, so that #1042 gets its fix. #679 was closed only because it conflicted ("please rebase on current main and reopen"), and it hasn't been reopened since. Tom's commit is kept as his own, with one small commit on top. `create_swapchain` (`gpu/vk/vk_main.cpp`) kept the **last** `B8G8R8A8_UNORM` / `R8G8B8A8_UNORM` surface format, whatever its colour space. It now only takes `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR` entries. ## Why Mesa's Wayland WSI (`src/vulkan/wsi/wsi_common_wayland.c`, `wsi_wl_display_determine_colorspaces` and `wsi_wl_surface_get_formats` on Mesa main) returns every format once per colour space: `SRGB_NONLINEAR_KHR` first, then `PASS_THROUGH_EXT` (always), then, when the compositor has `wp_color_manager_v1` (KWin, Hyprland, ...), every colour space it can map. The last UNORM entry is therefore never sRGB on RADV + Wayland, and the compositor shows the picture unmanaged: the grey, washed-out image in #1042. That issue now has three reports, all AMD + Wayland: RX 6600, RX 9070 XT on Arch, RX 9070 XT on CachyOS. X11 and NVIDIA list only sRGB entries. On an RTX 3060 under KDE Wayland it does not reproduce (my data point in #1042). ## Change from #679 #679 took the *first* sRGB match (`break`). The second commit keeps the old *last*-match rule, restricted to sRGB entries. On drivers that list only sRGB entries (NVIDIA, X11/XWayland, Windows), the chosen format is then exactly the one it is today, even when a driver lists both B8G8R8A8 and R8G8B8A8. The test (`tests/test_swapchain_colorspace.py`, picked up by `tests/run_all.py`, so the workflow edit from #679 is no longer needed) now checks for the sRGB condition and that the loop does not stop at the first match. ## Testing Tom tested the original on an RX 7900 XTX (RADV, Mesa 26.2.6, Hyprland HDR), see #679. I have no AMD card to test the rebase on, and I didn't build or run it locally: the conflict was only the neighbouring present-mode code, which keeps main's version. A test from someone on AMD + Wayland in #1042 would confirm it. Fixes #1042 AI-assisted (Claude Code) on behalf of metroid98: rebase, Mesa source check and write-up; not run locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_015DmSMGJSY9hHswALy4eqtQ
t0masz407 commented 2026-11-09 12:22:14 +00:00 (Migrated from github.com)

Tested on AMD: RX 7900 XTX, RADV (Mesa 26.2.6), Hyprland 0.56 with an HDR desktop (wp_color_manager_v1), native Wayland (SDL_VIDEODRIVER=wayland). The build is v2.0.7 plus both commits of this PR. Colours are normal, the same as on XWayland. Without the fix the picture is grey, as in #1042. The last-match rule is a good change, thanks for picking this up. I'll close my duplicate #1103 in favour of this one.

Tested on AMD: RX 7900 XTX, RADV (Mesa 26.2.6), Hyprland 0.56 with an HDR desktop (wp_color_manager_v1), native Wayland (SDL_VIDEODRIVER=wayland). The build is v2.0.7 plus both commits of this PR. Colours are normal, the same as on XWayland. Without the fix the picture is grey, as in #1042. The last-match rule is a good change, thanks for picking this up. I'll close my duplicate #1103 in favour of this one.
metroid98 commented 2026-11-09 12:29:59 +00:00 (Migrated from github.com)

Thanks @t0masz407, that's the AMD + Wayland confirmation this needed, and the fix is yours. (AI-assisted reply via Claude Code on my behalf.)

Thanks @t0masz407, that's the AMD + Wayland confirmation this needed, and the fix is yours. (AI-assisted reply via Claude Code on my behalf.)
hckrman101 (Migrated from github.com) approved these changes 2026-11-09 18:50:35 +00:00
hckrman101 (Migrated from github.com) left a comment

Looks good. Only nits, nothing blocking.

The format stays B8G8R8A8/R8G8B8A8_UNORM and only the colour space is filtered, so nothing changes for the present path: the swapchain is still TRANSFER_DST (plus SRC when allowed) and written by copy/blit, so there is no gamma change, no re-encode on write and no STORAGE-usage question. Where a driver lists only SRGB_NONLINEAR entries (NVIDIA, X11, Windows) the chosen entry is the same as before, since it is still the last match. If no UNORM/sRGB entry exists it falls back to formats[0], as it did. The test is picked up by run_all's glob, and it correctly drops the break expectation from #679. Context: this supersedes #1103 (same fix, first-match with break), which was closed in favour of this one.

One caveat: I haven't run it on AMD + Wayland. It would be good to get a confirmation in #1042 before or soon after merge.

Looks good. Only nits, nothing blocking. The format stays B8G8R8A8/R8G8B8A8_UNORM and only the colour space is filtered, so nothing changes for the present path: the swapchain is still TRANSFER_DST (plus SRC when allowed) and written by copy/blit, so there is no gamma change, no re-encode on write and no STORAGE-usage question. Where a driver lists only SRGB_NONLINEAR entries (NVIDIA, X11, Windows) the chosen entry is the same as before, since it is still the last match. If no UNORM/sRGB entry exists it falls back to formats[0], as it did. The test is picked up by run_all's glob, and it correctly drops the `break` expectation from #679. Context: this supersedes #1103 (same fix, first-match with `break`), which was closed in favour of this one. One caveat: I haven't run it on AMD + Wayland. It would be good to get a confirmation in #1042 before or soon after merge.
@ -417,2 +417,3 @@
for (const auto& f : formats) // (an UNORM target: the game's window textures are presented as they are)
if (f.format == VK_FORMAT_B8G8R8A8_UNORM || f.format == VK_FORMAT_R8G8B8A8_UNORM) fmt = f;
// (in sRGB: RADV on Wayland lists every format again with PASS_THROUGH_EXT and the compositor's colour spaces,
// and the last UNORM entry left the compositor an untagged surface, a grey picture. Still the last sRGB one, so
hckrman101 (Migrated from github.com) commented 2026-11-09 18:50:34 +00:00

Nit: the new comment says "in sRGB:" and sits right below the old "an UNORM target" comment, so it reads a bit tangled. Also the format is still UNORM here, only the colour space is sRGB_NONLINEAR (same for the PR title). Maybe: "(UNORM format, SRGB_NONLINEAR colour space: ...)".

Nit: the new comment says "in sRGB:" and sits right below the old "an UNORM target" comment, so it reads a bit tangled. Also the format is still UNORM here, only the colour space is sRGB_NONLINEAR (same for the PR title). Maybe: "(UNORM format, SRGB_NONLINEAR colour space: ...)".
metroid98 commented 2026-11-09 18:57:18 +00:00 (Migrated from github.com)

Thanks for the review. On the AMD + Wayland caveat: @t0masz407 tested this PR (both commits on v2.0.7) on an RX 7900 XTX / RADV Mesa 26.2.6 / Hyprland HDR with native Wayland, in the comment above: colours were normal, grey without the fix. (AI-assisted via Claude Code on my behalf.)

Thanks for the review. On the AMD + Wayland caveat: @t0masz407 tested this PR (both commits on v2.0.7) on an RX 7900 XTX / RADV Mesa 26.2.6 / Hyprland HDR with native Wayland, in the comment above: colours were normal, grey without the fix. (AI-assisted via Claude Code on my behalf.)
BiasedMonkey commented 2026-11-09 22:03:50 +00:00 (Migrated from github.com)

Confirmed on AMD + Wayland: RX 7700S (RADV NAVI33), Mesa 26.2.6, CachyOS, KDE Plasma Wayland (EDID colour management). On v2.0.11 the swapchain came out as R8G8B8A8_UNORM in colour space 1000104011 (ADOBERGB_LINEAR_EXT, the last of 90 pairs Mesa offered), and the picture was washed out: a readback of the swapchain image matched the game's own picture byte for byte, but on screen every value was lifted (17 -> 58, 46 -> 100, 102 -> 163, a second encode by KWin). The same change applied to v2.0.11 (sRGB colour spaces only) gives B8G8R8A8_UNORM / SRGB_NONLINEAR and the picture is right, on the title movie and in gameplay.

AI-assisted (Claude Code) on behalf of the reporter; tested by them on the machine above.

Confirmed on AMD + Wayland: RX 7700S (RADV NAVI33), Mesa 26.2.6, CachyOS, KDE Plasma Wayland (EDID colour management). On v2.0.11 the swapchain came out as `R8G8B8A8_UNORM` in colour space `1000104011` (`ADOBERGB_LINEAR_EXT`, the last of 90 pairs Mesa offered), and the picture was washed out: a readback of the swapchain image matched the game's own picture byte for byte, but on screen every value was lifted (17 -> 58, 46 -> 100, 102 -> 163, a second encode by KWin). The same change applied to v2.0.11 (sRGB colour spaces only) gives `B8G8R8A8_UNORM` / `SRGB_NONLINEAR` and the picture is right, on the title movie and in gameplay. AI-assisted (Claude Code) on behalf of the reporter; tested by them on the machine above.
Commenting is not possible because the repository is archived.
No description provided.