protonscr

New capability-code crashes for Vk drivers without any sparse queue families

dxvkclosed turnip
doitsujin/dxvk#4978 · opened 2025-06-06 by leegao · updated 2025-06-06 · 2 comments · github
Lleegao 2025-06-06 github

Note: this issue (or 2 issues) is discovered on Android PC emulation, but I believe it's a legitimate regression for any devices without any queues that support VK_QUEUE_SPARSE_BINDING_BIT

There are actually two bugs here, potentially affecting more than just Android drivers (though it seems to crash deterministically on some Android devices)

Bug 1: Regression in sparse queue family capability detection

Prior to https://github.com/doitsujin/dxvk/commit/5a4d89217249780e17451dbf6c7f678a4a0d472d, sparse queue functionality detection is done with a combination of

  1. https://github.com/doitsujin/dxvk/blob/5982b0c9fccbdccd7f12421b2b12ae6f1e0af29f/src/dxvk/dxvk_adapter.cpp#L152
    if (m_queueFamilies[graphicsQueue].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) {
      // Prefer using the graphics queue as a sparse binding queue
      sparseQueue = graphicsQueue;
    } else {
      sparseQueue = findQueueFamily(
        VK_QUEUE_SPARSE_BINDING_BIT,
        VK_QUEUE_SPARSE_BINDING_BIT);
    }
  1. https://github.com/doitsujin/dxvk/blob/5982b0c9fccbdccd7f12421b2b12ae6f1e0af29f/src/dxvk/dxvk_adapter.cpp#L526:
    DxvkAdapterQueueIndices queueFamilies = findQueueFamilies();
    queueFamiliySet.insert(queueFamilies.graphics);
    queueFamiliySet.insert(queueFamilies.transfer);

    if (queueFamilies.sparse != VK_QUEUE_FAMILY_IGNORED)
      queueFamiliySet.insert(queueFamilies.sparse);

Notably, sparse queues are conditionally enabled within the createinfo ONLY if the findQueueFamilies query didn't return VK_QUEUE_FAMILY_IGNORED

However, as of https://github.com/doitsujin/dxvk/commit/5a4d89217249780e17451dbf6c7f678a4a0d472d, the logic has changed to be unconditional enablement without the original VK_QUEUE_FAMILY_IGNORED check:

  1. https://github.com/doitsujin/dxvk/blob/10c9ef05e08ca28af18366c7f163bf838828300c/src/dxvk/dxvk_device_info.cpp#L551
    if (graphicsQueue.queueFamilyProperties.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) {
      m_queueMapping.sparse.family = m_queueMapping.graphics.family;
    } else {
      m_queueMapping.sparse.family = findQueueFamily(
        VK_QUEUE_SPARSE_BINDING_BIT,
        VK_QUEUE_SPARSE_BINDING_BIT);
    }

    // Actually enable all the queues
    enableQueue(m_queueMapping.graphics);
    enableQueue(m_queueMapping.transfer);
    enableQueue(m_queueMapping.sparse);
  1. https://github.com/doitsujin/dxvk/blob/10c9ef05e08ca28af18366c7f163bf838828300c/src/dxvk/dxvk_device_info.cpp#L577 which just enables the queue without checking if it's VK_QUEUE_FAMILY_IGNORED

In our case, from logs.txt you can see that the queue family for sparse is -1:

[19:23:19]  info:  Queues:
[19:23:19]  info:    Graphics : (0, 0)
[19:23:19]  info:    Transfer : (0, 0)
[19:23:19]  info:    Sparse   : (4294967295, 0)

I also added some debug logs to print out the deviceCreateInfo:

[19:23:19]  err:     queueCreateInfoCount: 2
[19:23:19]  err:       Queue[0]:
[19:23:19]  err:         sType: 2
[19:23:19]  err:         queueFamilyIndex: 0
[19:23:19]  err:         queueCount: 1
[19:23:19]  err:         pQueuePriorities: [ 1 ]
[19:23:19]  err:       Queue[1]:
[19:23:19]  err:         sType: 2
[19:23:19]  err:         queueFamilyIndex: 4294967295
[19:23:19]  err:         queueCount: 1
[19:23:19]  err:         pQueuePriorities: [ 1 ]

which tries to add a queueCreateInfo with a family index of -1/VK_QUEUE_FAMILY_IGNORED, causing vkCreateDevice to fail. I was able to confirm that changing the sparse queue enablement to

    // Actually enable all the queues
    enableQueue(m_queueMapping.graphics);
    enableQueue(m_queueMapping.transfer);
    // enableQueue(m_queueMapping.sparse);
    if (m_queueMapping.sparse.family != VK_QUEUE_FAMILY_IGNORED)
      enableQueue(m_queueMapping.sparse);

fixes the crash. However, if vkCreateDevice fails, it also shouldn't crash.

There's also a second bug that causes a null pointer exception / SEGFAULT when vkCreateDevice fails, which this bug inadvertently triggers.

Bug 2: SEGFAULT when vkCreateDevice fails due to change in error handling

The semantic of handling failed vkCreateDevice in dxvk::DxvkAdapter::createDevice was changed from throwing an exception (which downstream code still expects) to silently returning a nullptr (which downstream does not expect and does not check):

In the past, an error would've been thrown if vkCreateDevice fails: https://github.com/doitsujin/dxvk/blob/4f47cb21032926b1f136035e696dc286ebd3cdc7/src/dxvk/dxvk_adapter.cpp#L569

    if (vr != VK_SUCCESS)
      throw DxvkError("DxvkAdapter: Failed to create device");

Now, a nullptr is returned instead: https://github.com/doitsujin/dxvk/blob/10c9ef05e08ca28af18366c7f163bf838828300c/src/dxvk/dxvk_adapter.cpp#L228

    if (vr) {
      Logger::err(str::format("Failed to create Vulkan device: ", vr));
      return nullptr;
    }

However, it's still being called with the expectation that it throws instead of returning a nullptr, e.g.: https://github.com/doitsujin/dxvk/blob/4f47cb21032926b1f136035e696dc286ebd3cdc7/src/d3d11/d3d11_main.cpp#L103

    try {
      // ...
      Com<D3D11DXGIDevice> device = new D3D11DXGIDevice(
        pAdapter, nullptr, nullptr,
        dxvkInstance, dxvkAdapter, dxvkDevice, <-- is now nullable
        devFeatureLevel, Flags);
      // ...
    } catch (const DxvkError& e) {
      Logger::err("D3D11InternalCreateDevice: Failed to create D3D11 device");
      return E_FAIL;
    }

The D3D11DXGIDevice constructor then triggers:

  D3D11DXGIDevice::D3D11DXGIDevice(
          IDXGIAdapter*       pAdapter,
          ID3D12Device*       pD3D12Device,
          ID3D12CommandQueue* pD3D12Queue,
          Rc<DxvkInstance>    pDxvkInstance,
          Rc<DxvkAdapter>     pDxvkAdapter,
          Rc<DxvkDevice>      pDxvkDevice,
          D3D_FEATURE_LEVEL   FeatureLevel,
          UINT                FeatureFlags)
  : m_dxgiAdapter   (pAdapter),
    // ...
    m_dxvkDevice    (pDxvkDevice), <-- now nullable
    m_d3d11Device   (this, FeatureLevel, FeatureFlags),
    // ...

and the m_d3d11Device construction calls:

  D3D11Device::D3D11Device(
          D3D11DXGIDevice*    pContainer,
          D3D_FEATURE_LEVEL   FeatureLevel,
          UINT                FeatureFlags)
  : m_container         (pContainer),
    // ...
    m_dxvkDevice        (pContainer->GetDXVKDevice()), <-- now nullable
    m_dxvkAdapter       (m_dxvkDevice->adapter()),  <-- segfaults if dxvkDevice is null
    // ...

However, the user won't be able to play the game if vkCreateDevice fails, regardless of how it fails (segfaults vs exit(1))

Software information

TestD3D.exe

System information

  • GPU: Adreno 650
  • Driver: Turnip 25.0.0
  • Wine version:
  • DXVK version: Head as of 6/6/25 (v2.6.1-274-g10c9ef05)

Log files

Please attach Proton or Wine logs as a text file:

dxvk logs: logs.txt

Ddoitsujin maintainer 2025-06-06 github

Should be fixed in ca1aa2061f6bfce83d777e6124028df0511e3749, please confirm.

Given that every single desktop driver supports sparse binding, it's just not something we can test in any sane way whatsoever.

Lleegao 2025-06-06 github

I can confirm that bug #1 is fixed with this patch!

However, commenting out the sparse queue fix in enableQueue (just to validate the segfault fix) still causes a segfault for some reason, I'll keep debugging to see where the actual problem lies there and try to get an actual trace back

Edit: sorry, false alarm, the application I was testing with does not properly handle D3D9InterfaceEx::CreateDeviceEx not returning a D3D_OK status and that's where the NPE was coming from. I've confirmed with another test app that bug #2 has been fixed as well. Thank you for the super prompt fixes!

Nothing extracted yet.