Skip to content
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

Open
3 tasks done
flibitijibibo opened this issue May 25, 2023 · 27 comments
Open
3 tasks done

[native] Establish a libdxvk ABI #3451

flibitijibibo opened this issue May 25, 2023 · 27 comments

Comments

@flibitijibibo
Copy link
Contributor

flibitijibibo commented May 25, 2023

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:

  • GLFW and None WSI implementations cannot use the same fields with the same runtime library
  • SDL3 will eventually replace SDL2, and will hit the same problems as GLFW and None

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...

/* Don't get too excited, remember, this is the BAD example */

#define DXVK_WSI_VERSION 0

typedef enum
{
    DXVK_WSI_SDL2, /* Default */
    DXVK_WSI_SDL3,
    DXVK_WSI_GLFW,
    DXVK_WSI_NONE
} DXVKWSIPlatform;

typedef struct DXVKWindow
{
    uint32_t version; /* Application sets this to DXVK_WSI_VERSION */
    DXVKWSIPlatform platform;

    union
    {
#if DXVK_HAS_WSI_SDL2
        struct
        {
            SDL_Window *window;
        } sdl2;
#endif /* DXVK_HAS_WSI_SDL2 */
#if DXVK_HAS_WSI_SDL3
        struct
        {
            SDL_Window *window;
            /* Maybe something else...? */
        } sdl3;
#endif
        /* etc... */
        void* padding[16]; /* Up to 16 pointers are supported */
    };
} DXVKWindow;

... 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...

/* Sorry, still in Bad Example Land! */

CreateDXGIFactory1(
#ifdef DXVK_NATIVE
	&D3D_IID_IDXGIFactoryDXVK_SDL2,
#else
	&D3D_IID_IDXGIFactory1,
#endif
	&factory
);

... 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 have D3D_IID_IDXGIFactoryDXVK:

/* Okay, good-ish examples begin */

#define DXVK_WSI_VERSION 0

typedef struct DXVKWSI
{
    uint32_t version; /* Application sets this to DXVK_WSI_VERSION */
    /* Function pointers for junk like mode enumeration and dxvk's current WSI responsibilities */
} DXVKWSI;

typedef struct IDXGIFactoryDXVKVtbl
{
    /* IDXGIFactory7 declarations or whatever */

    HRESULT (STDMETHODCALLTYPE *SetWSI)(DXVKWSI *wsi); 
} IDXGIFactoryDXVKVtbl;

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!

@doitsujin
Copy link
Owner

doitsujin commented May 25, 2023

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 D3D11CreateDeviceAndSwapChain with a NULL adapter basically cannot work with anything other than the default path, so I'm not entrely sure if I understand the proposal correctly.

@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented May 25, 2023

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.)

@flibitijibibo
Copy link
Contributor Author

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:

FNA-XNA/FNA3D@cc09a34

@flibitijibibo
Copy link
Contributor Author

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:

  • Applications must create the DXGIFactory manually (does not need to be a special interface)
  • Applications are strongly encouraged to statically link a single-file header that hooks up the WSI library of their choosing, and call dxvkInitWSI(factory).
  • If SetWSI is not explicitly called, it will be assumed that SDL2 is the WSI

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.

@flibitijibibo
Copy link
Contributor Author

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:

FNA-XNA/FNA3D@f86b702

@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented Jun 2, 2023

Slapped together a draft of the documentation:

Integrating DXVK in a Linux Build

DXVK provides its own DirectX headers that allow for building existing client
code with as few changes as possible - this includes:

  • d3d9.h, d3d11.h, dxgi.h, etc.
  • A minimal windows.h implementation (basic typedefs, macros, etc.)
  • dxvk_wsi.h, used for custom Window System Integration

As-is, your client should build without changing any Direct3D code. You may
have to replace Win32 API calls for non-Win32 platforms (for example, DXVK does
not implement calls like MonitorFromWindow), so you are encouraged to design
your rendering abstraction layer to consider D3D as a multiplatform renderer,
rather than just a Windows renderer.

Window System Integration

Because non-Windows platforms will not be using the expected HWND window
handle, DXVK is designed to support custom window system APIs using the HWND
as a multiplatform opaque pointer. For example, applications can instead pass
an SDL_Window* pointer where appropriate, and DXVK will be able to process the
information correctly.

By default, DXVK assumes that native applications will be using SDL 2.0 as the
windowing API. However, it is possible to use any API of your choice by
providing a custom WSI function table to either the IDXGIFactory or the
IDirect3D9 handle. You can find the API in dxvk_wsi.h.

As for example implementations, the community has written a series of
single-file headers that allow quick integration of a number of popular APIs,
including SDL, GLFW, and a headless "None" WSI. These headers can be found
here.

WSI Example

Consider 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
assumes that SDL2 is used for window management. To explicitly set a WSI, an
IDXGIAdapter must be passed to this function, and therefore you must
explicitly create your own IDXGIFactory. If you already do this, or you're using
D3D9 (and thus create an IDirect3D9 instead), you can skip the following
example.

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
yet! For this example we will be explicitly setting SDL2 as our WSI. The
single-file headers are designed to work like this:

#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
each factory you create!

From then on, you can treat the HWND handle as the type of your choice - for
example, an SDL application using D3D11 might have something like this:

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);

@Joshua-Ashton
Copy link
Collaborator

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).

@Joshua-Ashton
Copy link
Collaborator

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;

  };

@flibitijibibo
Copy link
Contributor Author

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.

@Joshua-Ashton
Copy link
Collaborator

I would definitely like to avoid more app profiles (especially for native linux games) going forward. :-)

@doitsujin
Copy link
Owner

doitsujin commented Jun 4, 2023

@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 IDXVKDXGIFactorySomething::InitWindowSystem function to do so?

(all of which sounds perfectly fine to me, just want to confirm if this is what we're going for)

@Joshua-Ashton
Copy link
Collaborator

Yes, the mechanism for how an app will pick an interface is still a little TBD though.

@flibitijibibo flibitijibibo mentioned this issue Oct 6, 2023
3 tasks
@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented Oct 20, 2023

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:

  • dxvk: Refactor DxvkPlatformExts to wsi namespace
  • native: Load SDL via dlopen instead of linking to libSDL2
  • native: Load glfw via dlopen instead of linking to libglfw
  • wsi: Refactor dxvk::wsi to IDxvkWsi, selects a WSI backend on startup
  • meson: Make dxvk/wsi backends optional. At least one backend must always exist, but all backends can exist at the same time if available.
  • dxgi: Expose IDxvkWsi via QueryInterface

@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented Dec 7, 2023

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 DXVK_WSIDRIVER variable for GLFW and None to use.

@smcv
Copy link
Contributor

smcv commented Dec 7, 2023

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.

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:

It's a bit unfortunate that one of these is dxvk_ and the other is dxvk-. I've kept the naming from the Fedora packaging for now, but I'd be very tempted to change it to dxvk_d3d9.pc, dxvk_glfw_dxgi.pc and so on, to be consistent with the library names and avoid having two subtly different name-prefixes.

and perhaps this would be a good opportunity to make that change official?

@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented Dec 7, 2023

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.

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:

It's a bit unfortunate that one of these is dxvk_ and the other is dxvk-. I've kept the naming from the Fedora packaging for now, but I'd be very tempted to change it to dxvk_d3d9.pc, dxvk_glfw_dxgi.pc and so on, to be consistent with the library names and avoid having two subtly different name-prefixes.

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:

  • Add init/quit
  • Migrate getInstanceExtensions
  • Make backends C++ classes
  • Dynamically load SDL
  • Dynamically load glfw
  • Support multiple backends in one binary

@flibitijibibo
Copy link
Contributor Author

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!

@flibitijibibo
Copy link
Contributor Author

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(

@smcv
Copy link
Contributor

smcv commented Dec 10, 2023

I don't see any way to make Meson produce the right DLL name

What do you consider to be the right DLL name?

What you pasted above should produce a regular file libd3d10core.so.0.2.3 plus runtime symlink libd3d10core.so.0 and development symlink libd3d10core.so on Linux, and meanwhile should produce libd3d10core-0.dll on Windows, if I'm understanding correctly. (And similar for all the other libraries.) Is that what you want?

@flibitijibibo
Copy link
Contributor Author

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.

@flibitijibibo
Copy link
Contributor Author

Figured out a possible solution: #3743

@smcv
Copy link
Contributor

smcv commented Dec 12, 2023

If what you are aiming for is the situation I described above on Linux, but with an unversioned name like libd3d10core.dll (not what I described above) on Windows: then, yes, I think the only way to achieve that is to specify a version when building for Linux but leave the version unspecified when building for Windows, similar to #3743.

@flibitijibibo
Copy link
Contributor Author

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.

@smcv
Copy link
Contributor

smcv commented Jan 30, 2024

@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.

@flibitijibibo
Copy link
Contributor Author

#3834 reworks #3325, and I can confirm both Windows and Linux builds work as intended with the new changes.

@flibitijibibo
Copy link
Contributor Author

flibitijibibo commented May 21, 2024

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.

@smcv
Copy link
Contributor

smcv commented May 29, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants