-
Notifications
You must be signed in to change notification settings - Fork 898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[native] Establish a libdxvk ABI #3451
Comments
What exactly would this look like from an app PoV? If we have an interface that the app needs to query from the DXGI factory before interacting with WSI in any way then something like |
One of the main concessions I've currently made so far is that there is no way that applications will be able to use libdxvk without some kind of change, even if we were to retain the current SDL2 behavior - so one thing that has simplified things a bit is adding a requirement that CreateDevice must be passed an adapter, and therefore the application must create a DXGIFactory manually. This helps to define a more concrete framework that the WSI can be implemented within, while also adding code that actually ends up being useful to the application anyhow; in FNA3D for example we do this even on Windows because it's the only way to specify the GPU preference "officially", so axing the NULL adapter, while not 100% convenient, does help shape this implementation a whole lot better. (The NULL path would likely default to the old SDL2 behavior, for compatibility's sake.) |
I've made a branch of FNA3D that demonstrates what a port might look like. This demonstrates the changes needed for both the NULL and non-NULL adapter paths, and does so complete with dynamic loading of the D3D/DXGI libraries: |
Second revision of the port example: FNA-XNA/FNA3D@242c098 The main change is that I remembered that QueryInterface is a thing, so instead of having to construct a special DXGIFactory explicitly we only have to create any DXGIFactory of our choosing. Then, the single-file headers can call QueryInterface with a DXVK-specific IID that exposes the SetWSI() call. So, with the new design in mind:
This has the added benefit of working nicely with IDirect3D9, which DXVK can implement with the exact same interface; instead of a factory the application would pass the IDirect3D9 pointer and the code is otherwise identical. |
Apologies for repeat posting, but did a third revision since it wasn't much work to fake an IDxvkWsi interface to show the idea off a little better: |
Slapped together a draft of the documentation: Integrating DXVK in a Linux BuildDXVK provides its own DirectX headers that allow for building existing client
As-is, your client should build without changing any Direct3D code. You may Window System IntegrationBecause non-Windows platforms will not be using the expected By default, DXVK assumes that native applications will be using SDL 2.0 as the As for example implementations, the community has written a series of WSI ExampleConsider the following D3D call: /* Test to see if we meet the system requirements */
HRESULT res = D3D11CreateDevice(
NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
levels,
ARRAYSIZE(levels),
D3D11_SDK_VERSION,
NULL,
NULL,
NULL
);
CHECK_HRESULT(res) /* Pretend this actually means something */ By default, this creates a DXGI Factory in the background - as a result, this The above example will now look something like this: HRESULT res;
IDXGIFactory1 *factory;
IDXGIAdapter1 *adapter;
/* Create the adapter manually so we can set WSI for non-Win32 */
res = CreateDXGIFactory1(&D3D_IID_IDXGIFactory2, &factory);
CHECK_HRESULT(res)
/* Feel free to use IDXGIFactory6_EnumAdapterByGpuPreference instead... */
res = IDXGIFactory2_EnumAdapters1((IDXGIFactory2*) factory, 0, &adapter);
CHECK_HRESULT(res)
/* Test to see if we meet the system requirements */
res = D3D11CreateDevice(
(IDXGIAdapter*) adapter, /* This is the only change for this call! */
D3D_DRIVER_TYPE_HARDWARE,
NULL,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
levels,
ARRAYSIZE(levels),
D3D11_SDK_VERSION,
NULL,
NULL,
NULL
);
CHECK_HRESULT(res)
/* Don't forget to clean up! */
IDXGIAdapter1_Release(adapter);
IDXGIFactory1_Release(factory); This sets up the application to change the WSI, but we have not done it just #define DXVK_SDL2_IMPL /* Define this in exactly one build unit */
#include "dxvk_sdl2.h"
/* Later... */
void* yourFactoryOrIDirect3D9;
HRESULT res = dxvkInitWSI(yourFactoryOrIDirect3D9);
CHECK_HRESULT(res) As a result, the final example will look like this: HRESULT res;
IDXGIFactory1 *factory;
IDXGIAdapter1 *adapter;
/* Create the adapter manually so we can set WSI for non-Win32 */
res = CreateDXGIFactory1(&D3D_IID_IDXGIFactory2, &factory);
CHECK_HRESULT(res)
/* For non-Win32, set the custom WSI */
#ifndef _WIN32
res = dxvkInitWSI(factory);
CHECK_HRESULT(res)
#endif /* _WIN32 */
/* Feel free to use IDXGIFactory6_EnumAdapterByGpuPreference instead... */
res = IDXGIFactory2_EnumAdapters1((IDXGIFactory2*) factory, 0, &adapter);
CHECK_HRESULT(res)
/* Test to see if we meet the system requirements */
res = D3D11CreateDevice(
(IDXGIAdapter*) adapter,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
levels,
ARRAYSIZE(levels),
D3D11_SDK_VERSION,
NULL,
NULL,
NULL
);
CHECK_HRESULT(res)
/* Don't forget to clean up! */
IDXGIAdapter1_Release(adapter);
IDXGIFactory1_Release(factory); Note that the WSI does not persist statically, you will need to set the WSI for From then on, you can treat the HWND handle as the type of your choice - for HWND GetWindowForDXGI(SDL_Window *window)
{
#ifndef _WIN32
/* DXVK accepts the SDL_Window* directly */
return (HWND) window;
#else
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(window, &info);
return info.info.win.window;
#endif
}
...
DXGI_SWAP_CHAIN_DESC swapchainDesc;
swapchainDesc.OutputWindow = GetWindowForDXGI(sdlWindow); |
I was thinking about the "wsi interface provided by the app" idea, but I have some gripes with that approach. The major one being that if there are any subtle bugs in that wsi interface implementation and a game ships that, we can't fix it from the DXVK side. There are enough broken linux native ports (especially around windowing...) so I think we definitely need for the implementations to be resident in DXVK (but we can still use an interface internally). |
I imagine we would do something like this and converge the stuff into an interface per WSI: class IDXVKWsi {
public:
/*********************
* Monitor Interface
*********************/
/**
* \brief Default monitor
*
* \returns The default monitor
*/
virtual HMONITOR GetDefaultMonitor() = 0;
/**
* \brief Enumerators monitors on the system
*
* \returns The monitor of given index
*/
virtual HMONITOR EnumMonitors(uint32_t index) = 0;
/**
* \brief Enumerators monitors on the system
* \param [in] adapterLUID array of adapters' LUIDs
* \param [in] numLUIDs adapterLUID array size (0 for all monitors)
* \param [in] index Monitor index within enumeration
*
* \returns The monitor of given index
*/
virtual HMONITOR EnumMonitors(const LUID *adapterLUID[], uint32_t numLUIDs, uint32_t index) = 0;
/**
* \brief Get the GDI name of a HMONITOR
*
* Get the GDI Device Name of a HMONITOR to
* return to the end user.
*
* This typically looks like \.\\DISPLAY1
* and has a maximum length of 32 chars.
*
* \param [in] hMonitor The monitor
* \param [out] Name The GDI display name
* \returns \c true on success, \c false if an error occured
*/
virtual bool GetDisplayName(
HMONITOR hMonitor,
WCHAR (&Name)[32]) = 0;
/**
* \brief Get the encompassing coords of a monitor
*/
virtual bool GetDesktopCoordinates(
HMONITOR hMonitor,
RECT* pRect) = 0;
/**
* \brief Get the nth display mode
*
* \param [in] hMonitor The monitor
* \param [in] modeNumber The nth mode
* \param [out] pMode The resultant mode
* \returns \c true on success, \c false if the mode could not be found
*/
virtual bool GetDisplayMode(
HMONITOR hMonitor,
uint32_t modeNumber,
WsiMode* pMode) = 0;
/**
* \brief Get the current display mode
*
* This is the display mode right now.
*
* \param [in] hMonitor The monitor
* \param [out] pMode The resultant mode
* \returns \c true on success, \c false on failure
*/
virtual bool GetCurrentDisplayMode(
HMONITOR hMonitor,
WsiMode* pMode) = 0;
/**
* \brief Get the current display mode
*
* This is the display mode of the user's
* default desktop.
*
* \param [in] hMonitor The monitor
* \param [out] pMode The resultant mode
* \returns \c true on success, \c false on failure
*/
virtual bool GetDesktopDisplayMode(
HMONITOR hMonitor,
WsiMode* pMode) = 0;
/**
* \brief Get the EDID of a monitor
*
* Helper function to grab the EDID of a monitor.
* This is needed to get the HDR static metadata + colorimetry
* info of a display for exposing HDR.
*
* \param [in] hMonitor The monitor
* \returns \c EDID if successful, an empty vector if failure.
*/
virtual WsiEdidData GetMonitorEdid(HMONITOR hMonitor) = 0;
/**
* \brief Get the size of a monitor
*
* Helper function to grab the size of a monitor
* using getDesktopCoordinates to mirror the window code.
*/
inline void GetMonitorClientSize(
HMONITOR hMonitor,
UINT* pWidth,
UINT* pHeight) {
RECT rect = { };
getDesktopCoordinates(hMonitor, &rect);
if (pWidth)
*pWidth = rect.right - rect.left;
if (pHeight)
*pHeight = rect.bottom - rect.top;
}
/*********************
* Window Interface
*********************/
/**
* \brief The size of the window
*
* \param [in] hWindow The window
* \param [out] pWidth The width (optional)
* \param [out] pHeight The height (optional)
*/
virtual void GetWindowSize(
HWND hWindow,
uint32_t* pWidth,
uint32_t* pWeight) = 0;
/**
* \brief Resize a window
*
* \param [in] hWindow The window
* \param [in] pState The swapchain's window state
* \param [in] width The new width
* \param [in] height The new height
*/
virtual void ResizeWindow(
HWND hWindow,
DxvkWindowState* pState,
uint32_t width,
uint32_t weight) = 0;
/**
* \brief Sets the display mode for a window/monitor
*
* \param [in] hMonitor The monitor
* \param [in] hWindow The window (may be unused on some platforms)
* \param [in] mode The mode
* \returns \c true on success, \c false on failure
*/
virtual bool SetWindowMode(
HMONITOR hMonitor,
HWND hWindow,
const WsiMode& mode) = 0;
/**
* \brief Enter fullscreen mode for a window & monitor
*
* \param [in] hMonitor The monitor
* \param [in] hWindow The window (may be unused on some platforms)
* \param [in] pState The swapchain's window state
* \param [in] modeSwitch Whether mode switching is allowed
* \returns \c true on success, \c false on failure
*/
virtual bool EnterFullscreenMode(
HMONITOR hMonitor,
HWND hWindow,
DxvkWindowState* pState,
[[maybe_unused]]
bool modeSwitch) = 0;
/**
* \brief Exit fullscreen mode for a window
*
* \param [in] hWindow The window
* \param [in] pState The swapchain's window state
* \returns \c true on success, \c false on failure
*/
virtual bool LeaveFullscreenMode(
HWND hWindow,
DxvkWindowState* pState,
bool restoreCoordinates) = 0;
/**
* \brief Restores the display mode if necessary
*
* \returns \c true on success, \c false on failure
*/
virtual bool RestoreDisplayMode() = 0;
/**
* \brief The monitor a window is on
*
* \param [in] hWindow The window
* \returns The monitor the window is on
*/
virtual HMONITOR GetWindowMonitor(HWND hWindow) = 0;
/**
* \brief Is a HWND a window?
*
* \param [in] hWindow The window
* \returns Is it a window?
*/
virtual bool IsWindow(HWND hWindow) = 0;
/**
* \brief Update a fullscreen window's position/size
*
* \param [in] hMonitor The monitor
* \param [in] hWindow The window
* \param [in] forceTopmost Whether to force the window to become topmost again (D3D9 behaviour)
*/
virtual void UpdateFullscreenWindow(
HMONITOR hMonitor,
HWND hWindow,
bool forceTopmost) = 0;
/**
* \brief Create a surface for a window
*
* \param [in] hWindow The window
* \param [in] pfnVkGetInstanceProcAddr \c vkGetInstanceProcAddr pointer
* \param [in] instance Vulkan instance
* \param [out] pSurface The surface
*/
virtual VkResult CreateSurface(
HWND hWindow,
PFN_vkGetInstanceProcAddr pfnVkGetInstanceProcAddr,
VkInstance instance,
VkSurfaceKHR* pSurface) = 0;
}; |
That all makes sense to me, except for one thing: We could still override the vtable with patched functions if we knew of a title that used an old WSI impl and we recognized the executable name. I'm okay with either internal or external, but wanted to throw the idea of external out there in case WSI was looking to be a maintenance burden with all these backends coming up. |
I would definitely like to avoid more app profiles (especially for native linux games) going forward. :-) |
@Joshua-Ashton do I understand correctly that you want the implementation of that interface to reside within DXVK but off-load the responsibility of creating an instance of that interface to the app and have something like a (all of which sounds perfectly fine to me, just want to confirm if this is what we're going for) |
Yes, the mechanism for how an app will pick an interface is still a little TBD though. |
Trying to itemize this task as best as I can... would this be an acceptable route to getting this done? It's less the details and more about the process itself:
|
Took the first couple steps to getting this done:
I don't have experience with glfw so it'd help a lot to find someone who knows what they're doing to make sure we're not breaking that backend (and also to implement the dlopening for it...?). Once that's done all we have to do is the refactoring to support multiple backends in one binary and that should be the MVP for establishing ABI version 0 that all applications can use. The IDxvkWsi may not necessarily need to be present right from the start, as we could probably steal from SDL's design and expose a |
To accompany a new, stable ABI, it would be good if we can resurrect something similar to #3325 and use that to provide a new, stable build-time interface. On that PR, I wrote:
and perhaps this would be a good opportunity to make that change official? |
+1 to all of this! I've finished a first draft of the dynamic WSI system. It's very SDL/FNA3D-like and probably could be done with C++ interfaces or something along those lines, but I can confirm that this works for my applications at least: master...flibitijibibo:dxvk:wsi-dynamic It required ripping things up pretty badly for each step, so I may defer to Joshua for how the final patchset should look... EDIT: CI should succeed with this branch now! Current plan for new series:
|
Cleaned up the wsi-dynamic branch, #3738 is the result. It should pass CI and it works on the games I've tested it with! |
Took a guess at trying to establish the soname and wasn't able to get the Windows build to cooperate - the Linux/macOS conventions are fine but I don't see any way to make Meson produce the right DLL name without forcing the version numbers in. CMake does this by default so I don't really know if there's a good way to do this elsewhere... here's the patch if someone knows how to do this correctly: diff --git a/meson.build b/meson.build
index b571729d..6f52a690 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,10 @@
project('dxvk', ['c', 'cpp'], version : 'v2.3', meson_version : '>= 0.49', default_options : [ 'cpp_std=c++17', 'warning_level=2' ])
+dxvk_abi_version = '0'
+dxvk_major_version = '2'
+dxvk_minor_version = '3'
+dxvk_so_version = dxvk_abi_version + '.' + dxvk_major_version + '.' + dxvk_minor_version
+
cpu_family = target_machine.cpu_family()
platform = target_machine.system()
diff --git a/src/d3d10/meson.build b/src/d3d10/meson.build
index 51db067c..b68d41b1 100644
--- a/src/d3d10/meson.build
+++ b/src/d3d10/meson.build
@@ -23,6 +23,7 @@ d3d10_core_dll = shared_library('d3d10core'+dll_ext, d3d10_core_src, d3d10_core_
vs_module_defs : 'd3d10core'+def_spec_ext,
link_args : d3d10_core_ld_args,
link_depends : [ d3d10_core_link_depends ],
+ version : dxvk_so_version,
)
d3d10_core_dep = declare_dependency(
diff --git a/src/d3d11/meson.build b/src/d3d11/meson.build
index 9b51e6ea..1f86b00e 100644
--- a/src/d3d11/meson.build
+++ b/src/d3d11/meson.build
@@ -86,6 +86,7 @@ d3d11_dll = shared_library('d3d11'+dll_ext, dxgi_common_src + d3d11_src + d3d10_
vs_module_defs : 'd3d11'+def_spec_ext,
link_args : d3d11_ld_args,
link_depends : [ d3d11_link_depends ],
+ version : dxvk_so_version,
)
d3d11_dep = declare_dependency(
diff --git a/src/d3d9/meson.build b/src/d3d9/meson.build
index dd6b2316..06c1a5d1 100644
--- a/src/d3d9/meson.build
+++ b/src/d3d9/meson.build
@@ -65,6 +65,7 @@ d3d9_dll = shared_library('d3d9'+dll_ext, d3d9_src, glsl_generator.process(d3d9_
vs_module_defs : 'd3d9'+def_spec_ext,
link_args : d3d9_ld_args,
link_depends : [ d3d9_link_depends ],
+ version : dxvk_so_version,
)
d3d9_dep = declare_dependency(
diff --git a/src/dxgi/meson.build b/src/dxgi/meson.build
index a6e83b54..e293bf51 100644
--- a/src/dxgi/meson.build
+++ b/src/dxgi/meson.build
@@ -29,6 +29,7 @@ dxgi_dll = shared_library('dxgi'+dll_ext, dxgi_src, dxgi_res,
vs_module_defs : 'dxgi'+def_spec_ext,
link_args : dxgi_ld_args,
link_depends : [ dxgi_link_depends ],
+ version : dxvk_so_version,
)
dxgi_dep = declare_dependency( |
What do you consider to be the right DLL name? What you pasted above should produce a regular file |
For Linux and macOS this is correct, I don't think Windows can have those version numbers in the name - it would probably be okay if they didn't link between each other since we could always just rename them, but since they do depend on each other it means they no longer match up to Windows' official filenames for the DLLs. |
Figured out a possible solution: #3743 |
If what you are aiming for is the situation I described above on Linux, but with an unversioned name like |
Spent some time getting Fedora's dxvk-native package updated to 2.3, to see if anything new had come up since 1.9 that packagers would care about: https://src.fedoraproject.org/rpms/dxvk-native/tree/rawhide Thankfully the move to submodules actually made it a bit easier, though the directx header copies will probably raise an eyebrow or two. It's not wildly different compared to before though, so ¯_(ツ)_/¯ Aside from that, #3325 getting finished should make packaging this as straightforward as possible. Preemptively rebasing on #3738 and #3743 could help verify this. |
@flibitijibibo: feel free to take over #3325 if it would be useful to you, I'm unlikely to have much time to work on it any time soon. |
All known issues have been resolved as of the latest head revision! Now's a great time for packagers to try everything out and give feedback in case there's anything we missed. |
I tried packaging DXVK for Debian and the Steam Runtime, and didn't notice any remaining showstopper issues with the libraries. #4025 should be resolved at some point, but isn't a blocker. |
This is mostly a follow-up for #3321, #3322, and #3323. It's also an attempt to document the occasional one-off discussions Joshua and I have had re: making WSI work long-term. That said, this should not be considered a definitive document for how this will be implemented until all maintainers have formally approved it!
Remaining Tasks
Where we're at
Currently, dxvk-native acts pretty much like Win32 DXGI would, except we just treat the HWND window handle as an SDL_Window* instead and implement the rest with a custom WSI backend in dxvk. This has worked fine up until now, but will fall apart pretty soon:
What we can't do
My first gut reaction would have been to turn the HWND into a public structure, similar to that of SDL_SysWMInfo, where applications could inform dxvk of which WSI driver to use...
... but this doesn't work because of one specific question: When does the WSI initialize? Unlike SDL, which has a clear startup location at SDL_Init, D3D has a handful of calls that could be the first. There's each D3D version's CreateDevice, combined with the possibility of passing a window handle or not (usually just for feature level testing, but still), as well as CreateDXGIFactory1. From what I understand, a DXGI factory is made when one isn't available in the CreateDevice function, so I'm inclined to lean towards doing this in CreateDXGIFactory, but that of course doesn't have any parameters with which we could pass WSI information, and there are just way too many assumptions that have to be made for it to determine which WSI it should use.
The best option we have for a custom CreateDXGIFactory without adding new functions would be to expose custom interfaces, so one could do this...
... but then there's the question of just how many backends dxvk would actually need to support. This adds a lot of maintenance burden to the project that doesn't really have anything to do with implementing Direct3D over Vulkan, so I fully understand why this would be undesirable.
There's also one last detail, which is that libdxvk is technically out in the wild already, via Source 1 and FNA's Linux D3D11 Alpha. While not a huge deal for myself and Joshua to just update to the new ABI, there might still be some hesitation to break the ABIs on the existing sofiles, so no matter what we do we're kind of stuck with the current SDL2 path with the given sonames.
What we can try
The above work led me to one last idea, which was to offload WSI responsibility entirely to applications, so instead of there being
D3D_IID_IDXGIFactoryDXVK_SDL2
,D3D_IID_IDXGIFactoryDXVK_SDL3
, etc, we'd just haveD3D_IID_IDXGIFactoryDXVK
:Then, instead of having the WSI be in dxvk, we would instead just have single-file headers like "dxvk_sdl3.h" for platforms that are widely re-used, which would fill in this function table for developers automatically.
As for the current ABI, there are two things we can do to keep things as stable as possible:
First, establish a versioning scheme, so that new applications 100% always link to a new
libdxvk_dxgi1.so.0
, for example. Packagers will probably want this anyway, so this seems like a good time to get that over with. Second, we can actually keep the current SDL2 path as-is, and when applications do not use the DXVK interface, it will be assumed that the application intends to use DXVK's existing SDL2 path, as shipping applications currently do. Maybe some day that functionality can be deprecated, but for now this seems like the best route for keeping as many applications happy as possible.This is all super high-level, and Joshua and I haven't dug into this a whole lot (despite the wall of text suggesting otherwise), but I'm happy to get into the gritty details to make this all work!
The text was updated successfully, but these errors were encountered: