[PATCH v2 0/4] MR10874: d3d8: Do not support multisampling for 16-bit formats.
Star Wars Starfighter depends upon `Reset()` failing when it tries to enable multisampling, because it calls `LockRect()` on the back buffer, leaving it in WINED3D_LOCATION_SYSMEM which later hits "Not supported for multisample textures" in `wined3d_texture_gl_upload_data()`. -- v2: wined3d: Disallow locking of multisampled back buffers. d3d8: Do not support multisampling for 16-bit formats. d3d9/tests: Test format multisampling support. d3d8/tests: Test format multisampling support. https://gitlab.winehq.org/wine/wine/-/merge_requests/10874
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/d3d8/tests/device.c | 91 ++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/dlls/d3d8/tests/device.c b/dlls/d3d8/tests/device.c index 02383233cff..e2c4ae67748 100644 --- a/dlls/d3d8/tests/device.c +++ b/dlls/d3d8/tests/device.c @@ -9507,26 +9507,35 @@ static void test_clip_planes_limits(void) static void test_swapchain_multisample_reset(void) { D3DPRESENT_PARAMETERS present_parameters; + IDirect3DSurface8 *surface; IDirect3DDevice8 *device; + D3DLOCKED_RECT lr; IDirect3D8 *d3d; + unsigned int i; ULONG refcount; HWND window; HRESULT hr; + static const struct + { + const char *name; + D3DFORMAT format; + HRESULT expected_hr; + } + formats[] = + { + {"D3DFMT_R5G6B5", D3DFMT_R5G6B5, D3DERR_INVALIDCALL}, + {"D3DFMT_X1R5G5B5", D3DFMT_X1R5G5B5, D3DERR_INVALIDCALL}, + {"D3DFMT_A1R5G5B5", D3DFMT_A1R5G5B5, D3DERR_INVALIDCALL}, + {"D3DFMT_X8R8G8B8", D3DFMT_X8R8G8B8}, + {"D3DFMT_A8R8G8B8", D3DFMT_A8R8G8B8}, + }; + window = create_window(); ok(!!window, "Failed to create a window.\n"); d3d = Direct3DCreate8(D3D_SDK_VERSION); ok(!!d3d, "Failed to create D3D object.\n"); - if (IDirect3D8_CheckDeviceMultiSampleType(d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, - D3DFMT_A8R8G8B8, TRUE, D3DMULTISAMPLE_2_SAMPLES) == D3DERR_NOTAVAILABLE) - { - skip("Multisampling not supported for D3DFMT_A8R8G8B8.\n"); - IDirect3D8_Release(d3d); - DestroyWindow(window); - return; - } - if (!(device = create_device(d3d, window, NULL))) { skip("Failed to create 3D device.\n"); @@ -9538,19 +9547,59 @@ static void test_swapchain_multisample_reset(void) hr = IDirect3DDevice8_Clear(device, 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); - memset(&present_parameters, 0, sizeof(present_parameters)); - present_parameters.BackBufferWidth = 640; - present_parameters.BackBufferHeight = 480; - present_parameters.BackBufferFormat = D3DFMT_A8R8G8B8; - present_parameters.SwapEffect = D3DSWAPEFFECT_DISCARD; - present_parameters.hDeviceWindow = NULL; - present_parameters.Windowed = TRUE; - present_parameters.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES; - hr = IDirect3DDevice8_Reset(device, &present_parameters); - ok(hr == D3D_OK, "Failed to reset device, hr %#lx.\n", hr); + for (i = 0; i < ARRAY_SIZE(formats); ++i) + { + winetest_push_context("Test %u, format %s", i, formats[i].name); - hr = IDirect3DDevice8_Clear(device, 0, NULL, D3DCLEAR_TARGET, 0xffffffff, 0.0f, 0); - ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); + if (FAILED(hr = IDirect3D8_CheckDeviceMultiSampleType(d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + formats[i].format, TRUE, D3DMULTISAMPLE_2_SAMPLES))) + { + ok(hr == D3DERR_NOTAVAILABLE, "Unexpected hr %#lx.\n", hr); + skip("Multisampling not supported.\n"); + winetest_pop_context(); + continue; + } + + memset(&present_parameters, 0, sizeof(present_parameters)); + present_parameters.BackBufferWidth = 640; + present_parameters.BackBufferHeight = 480; + present_parameters.BackBufferFormat = formats[i].format; + present_parameters.SwapEffect = D3DSWAPEFFECT_DISCARD; + present_parameters.hDeviceWindow = window; + present_parameters.Windowed = TRUE; + present_parameters.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES; + hr = IDirect3DDevice8_Reset(device, &present_parameters); + todo_wine_if(formats[i].expected_hr == D3DERR_INVALIDCALL) + ok(hr == formats[i].expected_hr, "Unexpected hr %#lx.\n", hr); + + if (FAILED(hr)) + { + hr = IDirect3DDevice8_TestCooperativeLevel(device); + ok(hr == D3DERR_DEVICENOTRESET, "TestCooperativeLevel returned hr %#lx.\n", hr); + winetest_pop_context(); + continue; + } + + hr = IDirect3DDevice8_Clear(device, 0, NULL, D3DCLEAR_TARGET, 0xffffffff, 0.0f, 0); + ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); + + /* Lockable back buffer flag is allowed. */ + present_parameters.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + hr = IDirect3DDevice8_Reset(device, &present_parameters); + ok(hr == D3D_OK, "Failed to reset device, hr %#lx.\n", hr); + + hr = IDirect3DDevice8_GetBackBuffer(device, 0, D3DBACKBUFFER_TYPE_MONO, &surface); + ok(SUCCEEDED(hr), "GetBackBuffer failed, hr %#lx.\n", hr); + + /* But locking is not allowed. */ + hr = IDirect3DSurface8_LockRect(surface, &lr, NULL, 0); + todo_wine + ok(hr == D3DERR_INVALIDCALL, "Unexpected hr %#lx.\n", hr); + + IDirect3DSurface8_Release(surface); + + winetest_pop_context(); + } refcount = IDirect3DDevice8_Release(device); ok(!refcount, "Device has %lu references left.\n", refcount); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10874
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/d3d9/tests/device.c | 96 +++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/dlls/d3d9/tests/device.c b/dlls/d3d9/tests/device.c index 3dc9bff4dc2..3ecd75c4fe3 100644 --- a/dlls/d3d9/tests/device.c +++ b/dlls/d3d9/tests/device.c @@ -13224,53 +13224,87 @@ static void test_swapchain_multisample_reset(void) IDirect3DDevice9 *device; DWORD quality_levels; IDirect3D9 *d3d; + unsigned int i; ULONG refcount; HWND window; HRESULT hr; + static const struct + { + const char *name; + D3DFORMAT format; + } + formats[] = + { + {"D3DFMT_R5G6B5", D3DFMT_R5G6B5}, + {"D3DFMT_X1R5G5B5", D3DFMT_X1R5G5B5}, + {"D3DFMT_A1R5G5B5", D3DFMT_A1R5G5B5}, + {"D3DFMT_X8R8G8B8", D3DFMT_X8R8G8B8}, + {"D3DFMT_A8R8G8B8", D3DFMT_A8R8G8B8}, + {"D3DFMT_A2R10G10B10", D3DFMT_A2R10G10B10}, + }; + window = create_window(); ok(!!window, "Failed to create a window.\n"); d3d = Direct3DCreate9(D3D_SDK_VERSION); ok(!!d3d, "Failed to create a D3D object.\n"); - if (IDirect3D9_CheckDeviceMultiSampleType(d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, - D3DFMT_A8R8G8B8, TRUE, D3DMULTISAMPLE_2_SAMPLES, &quality_levels) == D3DERR_NOTAVAILABLE) + for (i = 0; i < ARRAY_SIZE(formats); ++i) { - skip("Multisampling not supported for D3DFMT_A8R8G8B8.\n"); - IDirect3D9_Release(d3d); - DestroyWindow(window); - return; - } + winetest_push_context("Test %u, format %s", i, formats[i].name); - if (!(device = create_device(d3d, window, NULL))) - { - skip("Failed to create a 3D device.\n"); - IDirect3D9_Release(d3d); - DestroyWindow(window); - return; - } + if (FAILED(hr = IDirect3D9_CheckDeviceMultiSampleType(d3d, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + formats[i].format, TRUE, D3DMULTISAMPLE_2_SAMPLES, &quality_levels))) + { + ok(hr == D3DERR_NOTAVAILABLE, "Unexpected hr %#lx.\n", hr); + skip("Multisampling not supported.\n"); + winetest_pop_context(); + continue; + } - hr = IDirect3DDevice9_Clear(device, 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); - ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); + if (!(device = create_device(d3d, window, NULL))) + { + skip("Failed to create a 3D device.\n"); + winetest_pop_context(); + goto done; + } - hr = IDirect3DDevice9_GetSwapChain(device, 0, &swapchain); - ok(hr == D3D_OK, "Failed to get the implicit swapchain, hr %#lx.\n", hr); - hr = IDirect3DSwapChain9_GetPresentParameters(swapchain, &d3dpp); - ok(hr == D3D_OK, "Failed to get present parameters, hr %#lx.\n", hr); - ok(d3dpp.MultiSampleType == D3DMULTISAMPLE_NONE, - "Got unexpected multisample type %#x.\n", d3dpp.MultiSampleType); - IDirect3DSwapChain9_Release(swapchain); + hr = IDirect3DDevice9_Clear(device, 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); + ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); - d3dpp.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES; - d3dpp.MultiSampleQuality = quality_levels - 1; - hr = IDirect3DDevice9_Reset(device, &d3dpp); - ok(hr == D3D_OK, "Failed to reset device, hr %#lx.\n", hr); + hr = IDirect3DDevice9_GetSwapChain(device, 0, &swapchain); + ok(hr == D3D_OK, "Failed to get the implicit swapchain, hr %#lx.\n", hr); + hr = IDirect3DSwapChain9_GetPresentParameters(swapchain, &d3dpp); + ok(hr == D3D_OK, "Failed to get present parameters, hr %#lx.\n", hr); + ok(d3dpp.MultiSampleType == D3DMULTISAMPLE_NONE, + "Got unexpected multisample type %#x.\n", d3dpp.MultiSampleType); + IDirect3DSwapChain9_Release(swapchain); - hr = IDirect3DDevice9_Clear(device, 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); - ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); + d3dpp.BackBufferFormat = formats[i].format; + d3dpp.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES; + d3dpp.MultiSampleQuality = quality_levels - 1; + hr = IDirect3DDevice9_Reset(device, &d3dpp); + ok(hr == D3D_OK, "Unexpected hr %#lx.\n", hr); - refcount = IDirect3DDevice9_Release(device); - ok(!refcount, "Device has %lu references left.\n", refcount); + hr = IDirect3DDevice9_Clear(device, 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); + ok(hr == D3D_OK, "Failed to clear, hr %#lx.\n", hr); + + /* Lockable back buffer flag is not allowed. */ + d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + hr = IDirect3DDevice9_Reset(device, &d3dpp); + todo_wine + ok(hr == D3DERR_INVALIDCALL, "Unexpected hr %#lx.\n", hr); + hr = IDirect3DDevice9_TestCooperativeLevel(device); + todo_wine + ok(hr == D3DERR_DEVICENOTRESET, "TestCooperativeLevel returned hr %#lx.\n", hr); + + refcount = IDirect3DDevice9_Release(device); + ok(!refcount, "Device has %lu references left.\n", refcount); + + winetest_pop_context(); + } + +done: IDirect3D9_Release(d3d); DestroyWindow(window); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10874
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/d3d8/device.c | 3 ++- dlls/d3d8/tests/device.c | 1 - dlls/wined3d/device.c | 7 +++++++ include/wine/wined3d.h | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dlls/d3d8/device.c b/dlls/d3d8/device.c index 036c60edc37..e2abd6e8ff4 100644 --- a/dlls/d3d8/device.c +++ b/dlls/d3d8/device.c @@ -308,7 +308,8 @@ static BOOL wined3d_swapchain_desc_from_d3d8(struct wined3d_swapchain_desc *swap swapchain_desc->auto_depth_stencil_format = wined3dformat_from_d3dformat(present_parameters->AutoDepthStencilFormat); swapchain_desc->flags - = (present_parameters->Flags & D3DPRESENTFLAGS_MASK) | WINED3D_SWAPCHAIN_ALLOW_MODE_SWITCH; + = (present_parameters->Flags & D3DPRESENTFLAGS_MASK) | WINED3D_SWAPCHAIN_ALLOW_MODE_SWITCH + | WINED3D_SWAPCHAIN_NO_16_BIT_MULTISAMPLING; swapchain_desc->refresh_rate = present_parameters->FullScreen_RefreshRateInHz; swapchain_desc->auto_restore_display_mode = TRUE; diff --git a/dlls/d3d8/tests/device.c b/dlls/d3d8/tests/device.c index e2c4ae67748..2969bdd45aa 100644 --- a/dlls/d3d8/tests/device.c +++ b/dlls/d3d8/tests/device.c @@ -9569,7 +9569,6 @@ static void test_swapchain_multisample_reset(void) present_parameters.Windowed = TRUE; present_parameters.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES; hr = IDirect3DDevice8_Reset(device, &present_parameters); - todo_wine_if(formats[i].expected_hr == D3DERR_INVALIDCALL) ok(hr == formats[i].expected_hr, "Unexpected hr %#lx.\n", hr); if (FAILED(hr)) diff --git a/dlls/wined3d/device.c b/dlls/wined3d/device.c index e754cc633b7..23cd94c6c23 100644 --- a/dlls/wined3d/device.c +++ b/dlls/wined3d/device.c @@ -5082,6 +5082,13 @@ HRESULT CDECL wined3d_device_reset(struct wined3d_device *device, TRACE("refresh_rate %u\n", swapchain_desc->refresh_rate); TRACE("auto_restore_display_mode %#x\n", swapchain_desc->auto_restore_display_mode); + /* d3d8 allows the lockable flag even though the backbuffer is not lockable. */ + if (swapchain_desc->flags & WINED3D_SWAPCHAIN_NO_16_BIT_MULTISAMPLING) + { + if (wined3d_get_format(device->adapter, swapchain_desc->backbuffer_format, 0)->byte_count == 2) + return WINED3DERR_INVALIDCALL; + } + if (swapchain_desc->backbuffer_bind_flags && swapchain_desc->backbuffer_bind_flags != WINED3D_BIND_RENDER_TARGET) FIXME("Got unexpected backbuffer bind flags %#x.\n", swapchain_desc->backbuffer_bind_flags); diff --git a/include/wine/wined3d.h b/include/wine/wined3d.h index cfb098adfb0..e82b078537e 100644 --- a/include/wine/wined3d.h +++ b/include/wine/wined3d.h @@ -920,6 +920,7 @@ enum wined3d_memory_segment_group #define WINED3D_SWAPCHAIN_NO_WINDOW_CHANGES 0x00040000u #define WINED3D_SWAPCHAIN_RESTORE_WINDOW_STATE 0x00080000u #define WINED3D_SWAPCHAIN_REGISTER_TOPMOST_TIMER 0x00100000u +#define WINED3D_SWAPCHAIN_NO_16_BIT_MULTISAMPLING 0x00200000u #define WINED3DDP_MAXTEXCOORD 8 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10874
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/d3d8/tests/device.c | 1 - dlls/d3d9/tests/device.c | 2 -- dlls/wined3d/device.c | 6 ++++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dlls/d3d8/tests/device.c b/dlls/d3d8/tests/device.c index 2969bdd45aa..f9bf9d05df8 100644 --- a/dlls/d3d8/tests/device.c +++ b/dlls/d3d8/tests/device.c @@ -9592,7 +9592,6 @@ static void test_swapchain_multisample_reset(void) /* But locking is not allowed. */ hr = IDirect3DSurface8_LockRect(surface, &lr, NULL, 0); - todo_wine ok(hr == D3DERR_INVALIDCALL, "Unexpected hr %#lx.\n", hr); IDirect3DSurface8_Release(surface); diff --git a/dlls/d3d9/tests/device.c b/dlls/d3d9/tests/device.c index 3ecd75c4fe3..2302cdc0cbb 100644 --- a/dlls/d3d9/tests/device.c +++ b/dlls/d3d9/tests/device.c @@ -13292,10 +13292,8 @@ static void test_swapchain_multisample_reset(void) /* Lockable back buffer flag is not allowed. */ d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; hr = IDirect3DDevice9_Reset(device, &d3dpp); - todo_wine ok(hr == D3DERR_INVALIDCALL, "Unexpected hr %#lx.\n", hr); hr = IDirect3DDevice9_TestCooperativeLevel(device); - todo_wine ok(hr == D3DERR_DEVICENOTRESET, "TestCooperativeLevel returned hr %#lx.\n", hr); refcount = IDirect3DDevice9_Release(device); diff --git a/dlls/wined3d/device.c b/dlls/wined3d/device.c index 23cd94c6c23..9f5f23b92a3 100644 --- a/dlls/wined3d/device.c +++ b/dlls/wined3d/device.c @@ -5088,6 +5088,10 @@ HRESULT CDECL wined3d_device_reset(struct wined3d_device *device, if (wined3d_get_format(device->adapter, swapchain_desc->backbuffer_format, 0)->byte_count == 2) return WINED3DERR_INVALIDCALL; } + else if (swapchain_desc->multisample_type && (swapchain_desc->flags & WINED3D_SWAPCHAIN_LOCKABLE_BACKBUFFER)) + { + return WINED3DERR_INVALIDCALL; + } if (swapchain_desc->backbuffer_bind_flags && swapchain_desc->backbuffer_bind_flags != WINED3D_BIND_RENDER_TARGET) FIXME("Got unexpected backbuffer bind flags %#x.\n", swapchain_desc->backbuffer_bind_flags); @@ -5176,6 +5180,8 @@ HRESULT CDECL wined3d_device_reset(struct wined3d_device *device, if (swapchain_desc->flags != current_desc->flags) { current_desc->flags = swapchain_desc->flags; + if (swapchain_desc->multisample_type) + current_desc->flags &= ~WINED3D_SWAPCHAIN_LOCKABLE_BACKBUFFER; update_swapchain_flags(swapchain->front_buffer); for (i = 0; i < current_desc->backbuffer_count; ++i) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10874
On Wed May 13 14:49:08 2026 +0000, Conor McCarthy wrote:
d3d9 is a bit different. It allows 16-bit for windowed devices, and doesn't allow the lockable flag. I haven't found any code in wined3d which checks for d3d8 vs d3d9. Is there an established way to do that and should it be done at all? The d3d10 module has very few tests, so I haven't tested it in d3d10. Rebased, and moved the implementation over to wined3d by adding a swapchain flag, but I'm not sure if that's the preferred way. I added d3d9 tests, and removed the d3d8 fullscreen tests because 16-bit modes are not available in Windows, so they all fail for that reason.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10874#note_139797
It turns out rejecting 16-bit formats won't fix the issue. The game calls `CheckDeviceMultiSampleType()` for `R8G8B8`, and if available it sets multisampling type 0x2 for format `R5G5B5` in a `D3DPRESENT_PARAMETERS` passed to `Reset()`. In Windows 11, multisampling is reported as available for fullscreen `R5G5B5` but not `R8G8B8`. The game depends on `CheckDeviceMultiSampleType()` failing because otherwise it tries to lock a multisampled backbuffer. This can't be fixed upstream. We should probably still have the constraints on locking, but don't need to reject 16-bit formats if they are supported, unless a reason comes up. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10874#note_139918
The game depends on `CheckDeviceMultiSampleType()` failing because otherwise it tries to lock a multisampled backbuffer. This can't be fixed upstream.
Huh, why not? I don't see anything wrong with reporting no multisampling support for that format. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10874#note_140055
On Sun May 17 23:36:52 2026 +0000, Elizabeth Figura wrote:
The game depends on `CheckDeviceMultiSampleType()` failing because otherwise it tries to lock a multisampled backbuffer. This can't be fixed upstream. Huh, why not? I don't see anything wrong with reporting no multisampling support for that format. I thought a "reject this format in particular" patch would be a little odd, but since this game has been getting away with this for a long time, it looks like `R8G8B8` is never supported on any Windows version. But then it would need to be tested in d3d9 and 11 too. Worth it?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10874#note_140296
On Sun May 17 23:36:51 2026 +0000, Conor McCarthy wrote:
I thought a "reject this format in particular" patch would be a little odd, but since this game has been getting away with this for a long time, it looks like `R8G8B8` is never supported on any Windows version. But then it would need to be tested in d3d9 and 11 too. Worth it? See 7c8dab24e6a2, and for that matter the other listed formats in the same commit. If 24-bit RGB is never supported for a render target, that makes things even easier; we can just remove that support bit.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10874#note_140510
participants (3)
-
Conor McCarthy -
Conor McCarthy (@cmccarthy) -
Elizabeth Figura (@zfigura)