Later patches are on https://gitlab.winehq.org/giomasce/wine/-/commits/chianti, though they still require some cleanup and cosmetic changes.
Currently the D3D12 swapchain accesses the low level Vulkan queue backing a vkd3d `ID3D12CommandQueue` using the `vkd3d_acquire_vk_queue()` call in order to submit frames for presentation. This causes a number of bugs because `vkd3d_acquire_vk_queue()` lets the caller submit to the Vulkan queue even if some operations are enqueued in the vkd3d internal op queue, which can lead to a frame being presented even if drawing work on it isn't finished (and not even queued, from the viewpoint of Vulkan!).
In order to fix this, I propose to introduce to add a few calls to vkd3d (currently [in this branch](https://gitlab.winehq.org/giomasce/vkd3d/-/commits/mongibello)), to allow the caller to enqueue calls to `vkQueueSubmit()` and `vkQueuePresentKHR()` through the vkd3d internal queue. The D3D12 swapchain would then use this new API instead of directly calling `vkQueueSubmit()` and `vkQueuePresentKHR()` as it does now. Eventually a similar call for `vkQueueWaitIdle()` should be added too. [That's already implemented in vkd3d](https://gitlab.winehq.org/giomasce/vkd3d/-/commit/b5ce799dbaf8c0fc13…, but not used yet in DXGI.
These changes also require introducing a worker thread for each D3D12 swapchain. The worker thread owns the Vulkan (backend) resources associated to the swapchain. Swapchain methods `Present()` and `ResizeBuffers()` do little more than pushing operations to a queue consumed by the worker thread. In this way the swapchain can immediately return on the calling thread, and the worker thread can wait on the operations submitted to the vkd3d queue, ensuring they're executed in the right order without fearing deadlocks. Notice that both `Present()` and `ResizeBuffers()` currently wait on a number of conditions (the availability of a swapchain image, its associated fence, device idleness when destroying Vulkan resources), and even if they shouldn't deadlock there could be a performance impact on the thread calling them. Since this is not a specific design objective, I didn't do any measurement, but hopefully the effect is at least nonnegative.
I considered (and partially implement) some alternative designs, but found them ultimately less satisfying:
* Just implement a generic callback in vkd3d ([in this way](https://gitlab.winehq.org/giomasce/vkd3d/-/commit/ad51ee2ad9597f275603…), that is queued and called with a `VkQueue` argument when dequeued. This is somewhat nicer for the vkd3d public interface, because only one call must be exposed (the one that enqueues a callback); however, DXGI would have to perform waits in the callbacks, which is a great recipe for deadlocks and for making performance harder to measure and debug.
* Use an event-based synchronization, but without a worker thread. Unfortunately that means that the swapchain can only make progress when called by the client. If for some reason a call to `Present()` must wait in the vkd3d internal queue and the client doesn't call the swapchain for some time, the frame presentation could be excessively delayed.
Patches in this MR don't implement those changes yet, but prepare the ground with some tests and light refactoring.
--
v2: dxgi: Split D3D12/Vulkan resource creation and destruction.
dxgi: Pass a VkImage to d3d12_swapchain_queue_present().
dxgi: Free the frontend images memory only once.
dxgi: Consider vk_format a frontend field.
dxgi/tests: Test that the present count is updated when Present() is called.
dxgi/tests: Test that the back buffer index is updated when Present() is called.
https://gitlab.winehq.org/wine/wine/-/merge_requests/3165