[PATCH 0/1] MR10567: wined3d: fix vk swapchain rendering too dark by supporting UNORM to SRGB...
wined3d: fix vk swapchain rendering too dark by supporting UNORM to SRGB conversion for brightness similar to gl This merge request addresses: https://bugs.winehq.org/show_bug.cgi?id=45364 Frostpunk is very dark Very dark rendering issues for Frostpunk seem to match the same problem for Vulkan renderer for "Against the Storm": - https://bugs.winehq.org/show_bug.cgi?id=58632 - https://bugs.winehq.org/attachment.cgi?id=80656 Running both games with gl renderer seems to fix excessive darkness: - https://bugs.winehq.org/attachment.cgi?id=80654 - https://bugs.winehq.org/attachment.cgi?id=80690 Vulkan renderer seems to be missing SRGB conversion handling availiable for GL renderer. GL renderer can be forced to render darker the same as Vulkan via this patch to ./dlls/wined3d/utils.c: ``` @@ -3133,7 +3146,7 @@ static BOOL init_format_texture_info(struct wined3d_adapter *adapter, struct win continue; copy_format(adapter, &srgb_format->f, &format->f); +#if 000 if (gl_info->supported[EXT_TEXTURE_SRGB] && !(adapter->d3d_info.wined3d_creation_flags & WINED3D_SRGB_READ_WRITE_CONTROL)) { @@ -3142,6 +3155,7 @@ static BOOL init_format_texture_info(struct wined3d_adapter *adapter, struct win format_set_caps(&srgb_format->f, WINED3D_FORMAT_CAP_SRGB_READ | WINED3D_FORMAT_CAP_SRGB_WRITE); query_internal_format(adapter, srgb_format, &format_texture_info[i], gl_info, TRUE); } +#endif } ``` This patch allows Vulkan renderer to perform SRGB converion for wined3d_swapchain_vk_select_vk_format(). Changing VkSwapchainCreateInfoKHR imageColorSpace from VK_COLOR_SPACE_SRGB_NONLINEAR_KHR for `wined3d_swapchain_vk_create_vulkan_swapchain()` had no effect despite enabling VK_EXT_swapchain_colorspace for dlls/win32u/vulkan.c `convert_instance_create_info():` https://docs.vulkan.org/refpages/latest/refpages/source/VkColorSpaceKHR.html Therefore converting UNORM to SRGB was chosen similar to how the GL renderer seems to handle it: ``` static const struct wined3d_format_srgb_info format_srgb_info[] = { {WINED3DFMT_R8G8B8A8_UNORM_SRGB, WINED3DFMT_R8G8B8A8_UNORM}, {WINED3DFMT_BC1_UNORM_SRGB, WINED3DFMT_BC1_UNORM}, {WINED3DFMT_BC2_UNORM_SRGB, WINED3DFMT_BC2_UNORM}, {WINED3DFMT_BC3_UNORM_SRGB, WINED3DFMT_BC3_UNORM}, {WINED3DFMT_B8G8R8A8_UNORM_SRGB, WINED3DFMT_B8G8R8A8_UNORM}, {WINED3DFMT_B8G8R8X8_UNORM_SRGB, WINED3DFMT_B8G8R8X8_UNORM}, {WINED3DFMT_BC7_UNORM_SRGB, WINED3DFMT_BC7_UNORM}, }; ``` Its unclear whether SRGB conversion should always happen or only for particular games so the patch leaves the Vulkan renderer without SRGB conversion by default. Therefore the patch adds a new WINE_D3D_CONFIG flag named "vk_swap_srgb" to force the Vulkan renderer to perform SRGB conversion for the swapchain. By default vk_swap_srgb is set to FALSE or 0: `WINE_D3D_CONFIG=vk_swap_srgb=0,renderer=vulkan` To force vk_swap_srgb to TRUE or 1: `WINE_D3D_CONFIG=vk_swap_srgb=1,renderer=vulkan` For example, launching Frostpunk via the following after the patch fixes the dark rendering for Vulkan and appears brighter similar to the GL renderer: `WINEDEBUG=-all mangohud --dlsym WINE_D3D_CONFIG=csmt=0x1,renderer=vulkan,vk_swap_srgb=1 WINEPREFIX=/home/any/wine_stianlow_wow64_new_pfx ~/tmp/wine_stianlow_wow64_new_install/bin/wine /home/any/wine_stianlow_wow64_new_pfx/drive_c/GOG\ Games/Frostpunk/Frostpunk.exe` ``` Some SRGB handling was added several years ago but not for mappnig UNORM to SRGB that this patch adds: 6f55c8d1c56 * wined3d: Use an sRGB fallback format for sRGB formats in wined3d_swapchain_vk_select_vk_format(). Author: Henri Verbeet <hverbeet@codeweavers.com> CommitDate: Wed Dec 9 16:29:28 2020 +0100 ``` If UNORM to SRGB should be enabled by default then this patch may be changed so that vk_swap_srgb defaults to TRUE. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10567
From: Stian Low <wineryyyyy@gmail.com> --- dlls/wined3d/swapchain.c | 11 ++++++++--- dlls/wined3d/utils.c | 13 +++++++++++++ dlls/wined3d/wined3d_main.c | 6 ++++++ dlls/wined3d/wined3d_private.h | 2 ++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/dlls/wined3d/swapchain.c b/dlls/wined3d/swapchain.c index 4c20587b81d..ac61a6b64a0 100644 --- a/dlls/wined3d/swapchain.c +++ b/dlls/wined3d/swapchain.c @@ -755,6 +755,7 @@ static VkFormat wined3d_swapchain_vk_select_vk_format(struct wined3d_swapchain_v { struct wined3d_device_vk *device_vk = wined3d_device_vk(swapchain_vk->s.device); const struct wined3d_swapchain_desc *desc = &swapchain_vk->s.state.desc; + enum wined3d_format_id backbuffer_format; const struct wined3d_vk_info *vk_info; struct wined3d_adapter_vk *adapter_vk; const struct wined3d_format *format; @@ -768,7 +769,11 @@ static VkFormat wined3d_swapchain_vk_select_vk_format(struct wined3d_swapchain_v vk_physical_device = adapter_vk->physical_device; vk_info = &adapter_vk->vk_info; - if ((format = wined3d_get_format(&adapter_vk->a, desc->backbuffer_format, WINED3D_BIND_RENDER_TARGET))) + backbuffer_format = desc->backbuffer_format; + if (wined3d_settings.vk_swap_srgb) + backbuffer_format = wined3d_get_format_srgb(desc->backbuffer_format); + + if ((format = wined3d_get_format(&adapter_vk->a, backbuffer_format, WINED3D_BIND_RENDER_TARGET))) vk_format = wined3d_format_vk(format)->vk_format; else vk_format = VK_FORMAT_B8G8R8A8_UNORM; @@ -800,7 +805,7 @@ static VkFormat wined3d_swapchain_vk_select_vk_format(struct wined3d_swapchain_v { /* Try to create a swapchain with format conversion. */ vk_format = get_swapchain_fallback_format(vk_format); - WARN("Failed to find Vulkan swapchain format for %s.\n", debug_d3dformat(desc->backbuffer_format)); + WARN("Failed to find Vulkan swapchain format for %s.\n", debug_d3dformat(backbuffer_format)); for (i = 0; i < format_count; ++i) { if (vk_formats[i].format == vk_format && vk_formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) @@ -810,7 +815,7 @@ static VkFormat wined3d_swapchain_vk_select_vk_format(struct wined3d_swapchain_v free(vk_formats); if (i == format_count) { - FIXME("Failed to find Vulkan swapchain format for %s.\n", debug_d3dformat(desc->backbuffer_format)); + FIXME("Failed to find Vulkan swapchain format for %s.\n", debug_d3dformat(backbuffer_format)); return VK_FORMAT_UNDEFINED; } diff --git a/dlls/wined3d/utils.c b/dlls/wined3d/utils.c index 0fb90ba3f18..1e2a3f09240 100644 --- a/dlls/wined3d/utils.c +++ b/dlls/wined3d/utils.c @@ -1803,6 +1803,19 @@ static const struct wined3d_format_srgb_info format_srgb_info[] = {WINED3DFMT_BC7_UNORM_SRGB, WINED3DFMT_BC7_UNORM}, }; +enum wined3d_format_id wined3d_get_format_srgb(enum wined3d_format_id format_id) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(format_srgb_info); ++i) + { + if (format_srgb_info[i].base_format_id == format_id) + { + return format_srgb_info[i].srgb_format_id; + } + } + return format_id; +} + static inline int get_format_idx(enum wined3d_format_id format_id) { unsigned int i; diff --git a/dlls/wined3d/wined3d_main.c b/dlls/wined3d/wined3d_main.c index 91d8dd567ff..402051ae2c3 100644 --- a/dlls/wined3d/wined3d_main.c +++ b/dlls/wined3d/wined3d_main.c @@ -129,6 +129,7 @@ struct wined3d_settings wined3d_settings = .max_sm_cs = UINT_MAX, .renderer = WINED3D_RENDERER_AUTO, .shader_backend = WINED3D_SHADER_BACKEND_AUTO, + .vk_swap_srgb = FALSE, }; enum wined3d_renderer CDECL wined3d_get_renderer(void) @@ -468,6 +469,11 @@ static BOOL wined3d_dll_init(HINSTANCE hInstDLL) ERR_(winediag)("Using the HLSL-based FFP backend.\n"); wined3d_settings.ffp_hlsl = tmpvalue; } + if (!get_config_key_dword(hkey, appkey, env, "vk_swap_srgb", &tmpvalue) && tmpvalue) + { + TRACE("Forcing Vulkan Swapchain SRGB conversions.\n"); + wined3d_settings.vk_swap_srgb = TRUE; + } } if (appkey) RegCloseKey( appkey ); diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h index fa758c53cc1..5997a1d0ea4 100644 --- a/dlls/wined3d/wined3d_private.h +++ b/dlls/wined3d/wined3d_private.h @@ -485,6 +485,7 @@ struct wined3d_settings bool check_float_constants; bool cb_access_map_w; bool ffp_hlsl; + bool vk_swap_srgb; }; extern struct wined3d_settings wined3d_settings; @@ -4627,6 +4628,7 @@ struct wined3d_format enum wined3d_format_id typeless_id; }; +enum wined3d_format_id wined3d_get_format_srgb(enum wined3d_format_id format_id); const struct wined3d_format *wined3d_get_format(const struct wined3d_adapter *adapter, enum wined3d_format_id format_id, unsigned int bind_flags); enum wined3d_format_id wined3d_get_typed_format_id(const struct wined3d_adapter *adapter, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10567
That's not what GL does. Note that init_srgb_formats() is not GL-specific code. Rather what's probably missing is support for SRGBWRITEENABLE and WINED3D_SAMP_SRGB_TEXTURE. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10567#note_135156
On Tue Apr 7 01:06:55 2026 +0000, Elizabeth Figura wrote:
That's not what GL does. Note that init_srgb_formats() is not GL-specific code. Rather what's probably missing is support for SRGBWRITEENABLE and WINED3D_SAMP_SRGB_TEXTURE. Thanks for taking a look.
GL handling of sRGB is not so trivial so I may have misstated how it actually works. From what I've gathered so far, `init_srgb_formats()` copies UNORM `wine_format` data to sRGB but maintains a unique IDs for sRGB for later processing depending on some criteria for checking for GL sRGB support. `struct wined3d_format_texture_info gl_srgb_internal` is used by `init_format_texture_info()` which seems to the major sRGB handling part missing for `wined3d_adapter_vk_init_format_info()`. Perhaps `init_format_texture_info()` for GL sRGB needs be implemented for VK within `init_vulkan_format_info()`? At the very least it would handle UNORM sRGB format mapping more granular than the at a more trivial and probably less robust swapchain level provided by the latest patch. Its unclear for now how to support sRGB handling for VK via SRGBWRITEENABLE and WINED3D_RS_SRGBWRITEENABLE so I'll have to take a closer look. `shader_glsl_apply_draw_state()` calls `state_srgbwrite()` with param `WINED3D_RS_SRGBWRITEENABLE` but that param is not used by `srgbwrite()` which just calls `glEnable/glDisable()` for `GL_FRAMEBUFFER_SRGB` depending on `wined3d_state` param. For VK, framebuffer attachmment VK_FORMAT may just need to be set correctly for UNORM vs sRGB so maybe wined3d_state is less applicable for VK than GL? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10567#note_135253
On Tue Apr 7 11:12:24 2026 +0000, Stian Low wrote:
Thanks for taking a look. GL handling of sRGB is not so trivial so I may have misstated how it actually works. From what I've gathered so far, `init_srgb_formats()` copies UNORM `wine_format` data to sRGB but maintains a unique IDs for sRGB for later processing depending on some criteria for checking for GL sRGB support. `struct wined3d_format_texture_info gl_srgb_internal` is used by `init_format_texture_info()` which seems to handle sRGB missing for `wined3d_adapter_vk_init_format_info()`. Perhaps `init_format_texture_info()` for GL sRGB needs be implemented for VK within `init_vulkan_format_info()`? At the very least it would handle UNORM sRGB format mapping more granular than the more trivial and probably less robust swapchain level provided by the latest patch. Its unclear for now how to support sRGB handling for VK via SRGBWRITEENABLE and WINED3D_RS_SRGBWRITEENABLE so I'll have to take a closer look. `shader_glsl_apply_draw_state()` calls `state_srgbwrite()` with param `WINED3D_RS_SRGBWRITEENABLE` but that param is not used by `srgbwrite()` which just calls `glEnable/glDisable()` for `GL_FRAMEBUFFER_SRGB` depending on `wined3d_state` param. For VK, framebuffer attachmment VK_FORMAT may just need to be set correctly for UNORM vs sRGB so maybe wined3d_state is less applicable for VK than GL? In d3d9, sRGB colour space conversion is something you toggle on and off when drawing. In GL and Vulkan, it's instead implemented using different formats, which do colour space conversion when bound. This means that an application can toggle sRGB and cause the data in the same texture to be interpreted as sRGB or linear.
In order to implement this in otherwise unextended GL, we need to potentially keep two different textures around, and to reinterpret, we download a texture to the CPU and then upload the same data to the other texture. This is slow and a bit cumbersome, and so if we have EXT_texture_sRGB_decode and EXT_framebuffer_sRGB, we can instead use a single sRGB texture and simply toggle between whether it's interpreted as sRGB and linear. (The former extension was actually motivated by D3D and you'll notice a familiar name on the authors list.) The same functionality is available in d3d10+, later GL, and Vulkan by using views. Views can generally arbitrarily reinterpret data as any other data as long as it takes up the same amount of space. Reinterpreting sRGB data as linear data is a case of this. So what we need to do for Vulkan is to create and bind linear views for sRGB textures as needed, probably from within stateblock.c.
From what I've gathered so far, `init_srgb_formats()` copies UNORM `wine_format` data to sRGB but maintains a unique IDs for sRGB for later processing depending on some criteria for checking for GL sRGB support.
It's precomputing a bunch of information about formats so it can be quickly looked up later. Since sRGB formats are almost identical to their non-sRGB equivalents, most of that information ends up being copied.
`struct wined3d_format_texture_info gl_srgb_internal` is used by `init_format_texture_info()` which seems to handle sRGB missing for `wined3d_adapter_vk_init_format_info()`.
That's for the old style sRGB emulation, which we don't need for Vulkan.
Perhaps `init_format_texture_info()` for GL sRGB needs be implemented for VK within `init_vulkan_format_info()`? At the very least it would handle UNORM sRGB format mapping more granular than the more trivial and probably less robust swapchain level provided by the latest patch.
All of that functionality is handled pretty much directly in init_vulkan_format_info(). See, GL format detection is a very complicated beast, because the idea of "just ask the driver whether this operation is supported" (i.e. ARB_internalformat_query, but actually we need ARB_internalformat_query2 for most things) wasn't always around, so we essentially have a huge hardcoded list of format capabilities based on what's supposed to be guaranteed by extensions. That then gets complicated by having to support different fallbacks for certain formats, including the sRGB stuff. We go to some extreme lengths to get cards to support the same level of d3d functionality as they do on Windows. With Vulkan it's a lot easier, because we just have format feature queries, not to mention almost everything we need is baked into version 1.0, so we don't need all those fallbacks. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10567#note_135303
participants (3)
-
Elizabeth Figura (@zfigura) -
Stian Low -
Stian Low (@stianlow)