This is related to the following issues:
Looking through the now open sourced WPF code, I found why the rendering issues in the Elite Dangerous Launcher occur:
https://github.com/dotnet/wpf/blob/c4e74175da6108104491e1ae64982444d7c8713d/src/Microsoft.DotNet.Wpf/src/WpfGfx/core/common/display.cpp#L1629
Basically, if D3D9 returns more adapters than EnumDisplayDevices, DotNet assumes a "mode change" and attempts initialisation again.
I wrote a tool, named d3d9Test.exe, that can demonstrate the issue. The source is in a gist located here:
https://gist.github.com/redmcg/5b11d2d18ff29da5d9d4886b2e1699a1
In my set-up, I have a dedicated Nvidia GPU and an Intel iGPU. When I have the iGPU disabled, I get the following output from the d3d9Test (with DXVK):
D3D9 says you have 2 adapter(s):
- \\.\DISPLAY1 (GeForce GTX 1050 Ti)
- \\.\DISPLAY1 (Intel(R) UHD Graphics 630 (Coffeelake 3x8 GT2))
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (not attached to desktop)
EnumDisplayDevices says 1 device(s) are OK
DotNet assumes a mode change, which causes issues in rendering, when m_uD3DAdapterCount > m_rgpDisplaysCount (2 > 1)
See https://github.com/dotnet/wpf/blob/c4e74175da6108104491e1ae64982444d7c8713d/src/Microsoft.DotNet.Wpf/src/WpfGfx/core/common/display.cpp#L1629
And the Elite Dangerous launcher has a rendering issue. But if I set my display to extended, I get the following output:
D3D9 says you have 2 adapter(s):
- \\.\DISPLAY1 (GeForce GTX 1050 Ti)
- \\.\DISPLAY1 (Intel(R) UHD Graphics 630 (Coffeelake 3x8 GT2))
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (OK)
EnumDisplayDevices says 2 device(s) are OK
DotNet should be happy
And the launcher renders OK.
So I believe DXVK should return only the one adapter if EnumDisplayDevices is returning only one Display.
Here's the output from the same setup on Windows.
With iGPU disabled:
D3D9 says you have 1 adapter(s):
- \\.\DISPLAY1 (NVIDIA GeForce GTX 1050 Ti)
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (not attached to desktop)
- 2: \\.\DISPLAY3 (not attached to desktop)
- 3: \\.\DISPLAY4 (not attached to desktop)
- 4: \\.\DISPLAY5 (not attached to desktop)
- 5: \\.\DISPLAY6 (not attached to desktop)
- 6: \\.\DISPLAY7 (not attached to desktop)
EnumDisplayDevices says 1 device(s) are OK
DotNet should be happy
With display extended:
D3D9 says you have 2 adapter(s):
- \\.\DISPLAY1 (NVIDIA GeForce GTX 1050 Ti)
- \\.\DISPLAY5 (Intel(R) UHD Graphics 630)
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (not attached to desktop)
- 2: \\.\DISPLAY3 (not attached to desktop)
- 3: \\.\DISPLAY4 (not attached to desktop)
- 4: \\.\DISPLAY5 (OK)
- 5: \\.\DISPLAY6 (not attached to desktop)
- 6: \\.\DISPLAY7 (not attached to desktop)
EnumDisplayDevices says 2 device(s) are OK
DotNet should be happy
Happy to provide additional logging if required.
Yes, native D3D9 enumerates by displays and it's singular main adapter.
Currently we just enumerate by adapters even if they do not have a respective display -- this is super useful on iGPU and multi GPU setups (especially in the former where display attachment is kinda questionable given the whole PRIME crap). But you are right in that this does not match native behaviour...
So to fix this we'd need to implement proper multi-monitor support (which is something we've been meaning to do for a while but I'm not entirely sure if the Wine infrastructure is there right now,) as well as matching the native behaviour for adapter enumeration.
Ideally I'd like to leave in the old behaviour as some option too given how convenient it is for me (ie. testing games and traces quickly on AMD and NV, where the latter has no display attached) and probably others.
I am curious, what is the behaviour of Elite: Dangerous' launcher when
// HKCU\Software\Microsoft\Avalon.Graphics\MultiAdapterSupport
// or
// HKLM\Software\Microsoft\Avalon.Graphics\MultiAdapterSupport
-> 0
I've tested that with dotnet48 and that resolves the issue (but not with dotnet40, they must of introduced this afterwards).
I can also work around the problem using the DXVK_FILTER_DEVICE_NAME environment variable.
Shame, I thought setting might be more sane than potentially breaking every/some iGPU and multi GPU setup people are using right now. Blegh.
It's a valid work-around. I've documented it here:
https://github.com/redmcg/wine/wiki#launcher-doesnt-show-the-play-button
dotnet40 is manually installed anyway, so it's just as easy to install dotnet48.
But you do need to set a value for every executable - so it'd be set on a per-game basis.
We'd also need some way of finding what GDI DISPLAYs a Vulkan adapter is directly attached to. This is gonna be pretty messy and hacky, and I was hoping it was not going to be necessary (cause it sucks from every single aspect) which is partially why I've avoided it so far :-/
What is the name of the wpf dll that calls that function btw? Ie. frog.dll
I have a terrible idea for a hack.
Do you mean this one? wpfgfx_v0400.dll
I'm pretty sure that's what the code at https://github.com/dotnet/wpf/tree/c4e74175da6108104491e1ae64982444d7c8713d/src/Microsoft.DotNet.Wpf/src/WpfGfx is compiled in to.
Does this workaround the issue (not suggesting this as any form of real fix btw, more curiosity)
UINT STDMETHODCALLTYPE D3D9InterfaceEx::GetAdapterCount() {
#ifdef _MSC_VER
void* returnAddress = _ReturnAddress();
#else
void* returnAddress = __builtin_return_address();
#endif
MEMORY_BASIC_INFORMATION mbi;
if (::VirtualQuery(returnAddress, &mbi, sizeof(mbi))) {
HMODULE module = reinterpret_cast<HMODULE>(mbi.AllocationBase);
WCHAR moduleName[MAX_PATH + 1] = {};
::GetModuleFileNameW(module, moduleName, MAX_PATH);
if (wcsstr(moduleName, L"wpfgfx_") != nullptr)
return 1;
}
return UINT(m_adapters.size());
}
Yep - that works too
Edit: FYI - I had to pass a UINT value to __builtin_return_address to get it to compile. i.e:
void* returnAddress = __builtin_return_address(0);
@redmcg Can you try with this branch?
https://github.com/doitsujin/dxvk/tree/d3d9-display-enum
My work there is not fully done, but it should be safe enough to test.
Thanks @Joshua-Ashton, I tried the Elite Dangerous Launcher with both:
and it rendered correctly on both.
However, I did notice a couple of things when running d3d9Test.exe that are worth pointing out.
When set to extended I get the following output:
D3D9 says you have 2 adapter(s):
- \\.\DISPLAY2 (GeForce GTX 1050 Ti)
err: D3D9Adapter::GetAdapterIdentifier: Failed to query display info
- \\.\DISPLAY2 (GeForce GTX 1050 Ti)
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (OK)
EnumDisplayDevices says 2 device(s) are OK
DotNet should be happy
So, there's the error, and the same adapter is shown twice.
And with the iGPU disabled I got:
D3D9 says you have 1 adapter(s):
- \\.\DISPLAY2 (GeForce GTX 1050 Ti)
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (not attached to desktop)
EnumDisplayDevices says 1 device(s) are OK
DotNet should be happy
So the adapter is listed as DISPLAY2 which is also listed as 'not attached to desktop'.
When I go Settings -> Devices -> Screen Display in Gnome, it shows the Nvidia as being DISPLAY2. So maybe Wine should be showing DISPLAY1 as unattached and DISPLAY2 as OK.
that doesn't make much sense to me given i only report displays that are OK...
hmmm
oh right
I made a typo
This should be fixed now
Nailed it - thanks @Joshua-Ashton. All works perfectly now.
Output for extended display is now:
D3D9 says you have 2 adapter(s):
- \\.\DISPLAY1 (GeForce GTX 1050 Ti)
- \\.\DISPLAY2 (Intel(R) UHD Graphics 630 (CFL GT2))
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (OK)
EnumDisplayDevices says 2 device(s) are OK
DotNet should be happy
and when the iGPU is disabled:
D3D9 says you have 1 adapter(s):
- \\.\DISPLAY1 (GeForce GTX 1050 Ti)
EnumDisplayDevices:
- 0: \\.\DISPLAY1 (OK)
- 1: \\.\DISPLAY2 (not attached to desktop)
EnumDisplayDevices says 1 device(s) are OK
DotNet should be happy
And of course the Elite Dangerous Launcher is rendering correctly.
Thanks again.
No worries! Have fun! :)